I have a custom icon element that is only displayed when its specific row in the table is hovered over, but when I scroll down without moving my mouse it doesn't update the hover and maintains the button on the screen and over my table's header. How can I make sure this doesn't happen?
export const StyleTr = styled.tr`
z-index: ${({ theme }) => theme.zIndex.userMenu};
&:hover {
background-color: ${({ theme, isData }) =>
isData ? theme.colors.primary.lighter : theme.colors.white};
div {
visibility: visible;
}
svg[icon] {
display: initial;
}
}
`;
I was just working on something similar to this for a web scraper recently.
Something like this should work:
function checkIfIconInViewport() {
// define current viewport (maximum browser compatability use both calls)
const viewportHeight =
window.innerHeight || document.documentElement.clientHeight;
//Get our Icon
let icon = document.getElementById('icon');
let iPos = icon.getBoundingClientRect();
//Show if any part of icon is visible:
if (viewportHeight - iPos.top > 0 && iPos.bottom > 0) {
icon.style.visibility = visibile;
}
else { icon.style.visibility = hidden; }
//Show only if all of icon is visible:
if (iPos.bottom > 0 && iPos.top >= 0) {
{
icon.style.visibility = visibile;
}
else { icon.style.visibility = hidden; }
//Add && iPos.bottom <= viewportHeight to the if check above for very large elements.
{
//Run function everytime that the window is scrolled.
document.addEventListener('scroll', checkIfIconInViewport);
Basically, every time a scroll event happens, we just check to see if the top & bottom of our element (the icon in your case) are within the bounds of the viewport.
Negative values, or values greater than the viewport's height mean that the respective portion of the element is outside the viewport's boundary.
Hopefully this helps! If you are dealing with a large quantity of objects, it may make sense to bundle the objects you are tracking together into an array and check each of them in a single function call to avoid saving function definitions for each individual object.
Edit: I just realized that I misunderstood your issue a bit. I think you can get by with just the bottom part of the code, and when a scroll event happens, set the icon's visibility to hidden. Assuming you want to hide it whenever the user scrolls?
Have you tried getting the scroll position of the DOM, then disabling (removing) the element once a certain scroll position is reached?
Related
I have a scrollable list of elements on the page, divided into sections. Each section is associated with a tab of the section name above the list. When I click on a particular Tab, the list scrolls automatically to the section of the tabs name.
<Tab value="one" active={current === 'one'} onClick={() => handlerScroll(sect1, 'one')}>
Section1
</Tab>
This is how Tab's press is processed and the scroll occurs - I change the state to the selected value, and I scroll through the list using scrollIntoView to the beginning of the selected section, referring to the section through ref:
сonst [current, setCurrent] = React.useState('one');
const handlerScroll = (tab, current) => {
setCurrent(current);
tab.current.scrollIntoView({ block: "start", behavior: "smooth" });
};
The first argument is ref to the clicked section of the list:
const sect1 = useRef();
<div ref={sect1}>
</div>
The scroll works. BUT, when i clicked, the browser window "scrolls" to the top of the list (the page literally descends to the top border of the div, where the entire list is located), and only then the list itself scroll to the clicked section.
Is it possible to somehow disable the scrolling of the browser window, so that the page itself does not move anywhere, but only the contents of the list change and scroll when you press Tab?
scrollIntoView will scroll the whole page to the target element.
I suggest you scroll the child element relative to its' parent element.
function scrollParentToChild(parent, child) {
// Where is the parent on page
var parentRect = parent.getBoundingClientRect();
// What can you see?
var parentViewableArea = {
height: parent.clientHeight,
width: parent.clientWidth
};
// Where is the child
var childRect = child.getBoundingClientRect();
// Is the child viewable?
var isViewable = (childRect.top >= parentRect.top) && (childRect.bottom <= parentRect.top + parentViewableArea.height);
// if you can't see the child try to scroll parent
if (!isViewable) {
// Should we scroll using top or bottom? Find the smaller ABS adjustment
const scrollTop = childRect.top - parentRect.top;
const scrollBot = childRect.bottom - parentRect.bottom;
if (Math.abs(scrollTop) < Math.abs(scrollBot)) {
// we're near the top of the list
parent.scrollTop += scrollTop;
} else {
// we're near the bottom of the list
parent.scrollTop += scrollBot;
}
}
}
And you can achieve the smooth behavior with
.parent {
scroll-behavior: smooth;
}
Another solution you can use scrollintoviewifneeded instead of scrollIntoView but it's not supported on Firefox and IE at all.
Resource
I've been learning Ruby on Rails and Javascript and trying to learn to animate things as I go.
I have a series of bootstrap cards generated by an Articles controller and shown by an article partial in an index view, where the list of cards (representing posts/messages) proceeds down the page. I want these cards to fade into view as they are scrolled into.
I start the card opacity at 0 with css:
$bg_color: #23282e;
$corner_radius: 5px;
.card {
border-radius: $corner_radius;
opacity: 0;
}
and fade it to 1 with javascript:
function fadeCardsIn() {
const window_top = $(window).scrollTop()
const window_bottom = $(window).scrollTop() + $(window).innerHeight()
console.log(`Viewport: ${window_top} to ${window_bottom}.`)
const cards = $(".card")
let count = 0
cards.each(function() {
count += 1
const card = $(this)
const card_top = card.offset().top;
const card_bottom = card.offset().top + card.outerHeight()
// If our card is in the range of the window viewport.
if ((window_bottom > card_top) && (window_top < card_bottom)) {
card.fadeTo(1000, 1, "swing")
console.log(`Showing card ${count}.`)
} else {
card.fadeTo(1000, 0, "swing")
}
})
}
$(document).ready(fadeCardsIn)
$(window).scroll(fadeCardsIn)
I've taken the bits that should allow things to function without all the rails backend and without the partial, with several cards copied/pasted in the html to simulate what I'm working with in the rendered view:
https://jsfiddle.net/n9kemt3L/5/
On load, the javascript function works as expected with the first few cards in the viewport fading in. However on scroll, based on the console log, the correct cards are "showing" and should be animating/fading in, but they either never fade in, or fade in / fade out very late (sometimes upwards of 30+ seconds later of not scrolling).
I'm assuming calling the fadeTo function multiple times on the same cards by scrolling is causing an issue, possibly calling fadeTo many times before the first fadeTo has even completed, but I'm not sure.
How should I go about beginning to solve this? Any suggestions to nudge me in the right direction are very welcome.
If the fadeTo excess calls are breaking the animations, then I imagine I might need to add some sort of state to know when each card is being animated into/out of, but I don't know where I should start with that if that were the case, as each card is generated by rails and served via pagination, and I'm just iterating over each card on the final rendered with jQuery's each() function.
On another note, I haven't been able to get fadeIn and fadeOut to function at all in this setup, hence me making use of fadeTo instead.
Is there a better way to go about this other than calling this sort of function on scroll?
Edit: I managed to get the functionality I was aiming for by simply removing the else statement.
cards.each(function() {
count += 1
const card = $(this)
const card_top = card.offset().top;
const card_bottom = card.offset().top + card.outerHeight()
// If our card is in the range of the window viewport.
if ((window_bottom > card_top) && (window_top < card_bottom)) {
console.log(`Showing card ${count}.`)
card.fadeTo(500, 1, "swing",function(){console.log(`Loaded card ${count}`)})
But I am still wondering if there's a better way to do this, as this each loop does fire off every single time I scroll.
Definitely feel like you're on the right path.
When I make things fade in from the top, bottom, left or right I usually do not explicitly give them an initial opacity of 0 in their own class.
Rather I create an animation class that has an an initial opacity of 0 and then assign it to them.
Like so...
HTML
<div class='card'>im a card example </div>
CSS
.card {
}
#keyframes fromRight {
0% {
transform: translateX(50px);
opacity: 0;
}
100% {
transform: translateX(0);
opacity: 1;
}
}
Javascript/jQuery
$(document).ready(function() {
$(window).scroll(function() {
let position = $(this).scrollTop();
console.log(position)
// specify your position by inspecting the page
if (position >= 1100) {
$('.card').addClass('fromRight');
}
});
})
Im creating a fixed header where on load, the logo is flat white. On scroll, it changes to the full color logo.
However, when scrolling back to the top, it stays the same colored logo instead of going back to white.
Here's the code (and a pen)
$(function() {
$(window).scroll(function() {
var navlogo = $('.nav-logo-before');
var scroll = $(window).scrollTop();
if (scroll >= 1) {
navlogo.removeClass('.nav-logo-before').addClass('nav-logo-after');
} else {
navlogo.removeClass('.nav-logo-after').addClass('nav-logo-before');
}
});
});
http://codepen.io/bradpaulp/pen/gmXOjG
There's a couple of things here:
1) You start with a .nav-logo-before class but when the logo becomes black you remove that class and then try to get the same element using a class selector that doesn't exist anymore
2) removeClass('.nav-logo-before') is different than removeClass('nev-logo-before), notice the "." in the first selector.
3) You get the element using the $('.selector')in every scroll event, this can be a performance issue, it's better to cache them on page load and then use the element stored in memory
4) It's not a good practice to listen to scroll events as this can be too performance demanding, it's usually better to use the requestAnimationFrame and then check if the scroll position has changed. Using the scroll event it could happen that you scroll up really fast and the scroll event doesn't happen at 0, so your logo won't change. With requestAnimationFrame this can't happen
$(function() {
var navlogo = $('.nav-logo');
var $window = $(window);
var oldScroll = 0;
function loop() {
var scroll = $window.scrollTop();
if (oldScroll != scroll) {
oldScroll = scroll;
if (scroll >= 1) {
navlogo.removeClass('nav-logo-before').addClass('nav-logo-after');
} else {
navlogo.removeClass('nav-logo-after').addClass('nav-logo-before');
}
}
requestAnimationFrame(loop);
}
requestAnimationFrame(loop);
});
body {
background-color: rgba(0,0,0,.2);
}
.space {
padding: 300px;
}
.nav-logo-before {
content: url(https://image.ibb.co/kYANyv/logo_test_before.png)
}
.nav-logo-after {
content: url(https://image.ibb.co/jYzFJv/logo_test_after.png)
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div>
<img class="nav-logo nav-logo-before">
</div>
<div class="space">
</div>
Dont need to add the dot . in front of the class name in removeClass and addClass:
Use this:
navlogo.removeClass('nav-logo-before')
Secondly, you are removing the class that you are using to get the element in the first place.
I have an updated codepen, see if this suits your needs: http://codepen.io/anon/pen/ZeaYRO
You are removing the class nav-logo-before, so the second time the function runs, it can't find any element with nav-logo-before.
Just give a second class to your navlogo element and use that on line 3.
Like this:
var navlogo = $('.second-class');
working example:
http://codepen.io/anon/pen/ryYajx
You are getting the navlogo variable using
var navlogo = $('.nav-logo-before');
but then you change the class to be 'nav-logo-after', so next time the function gets called you won't be able to select the logo using jquery as it won't have the '.nav-logo-before'class anymore.
You could add an id to the logo and use that to select it, for example.
Apart from that, removeClass('.nav-logo-before') should be removeClass('nav-logo-before') without the dot before the class name.
The problem is that you removes nav-logo-before and then you want to select element with such class but it doesn't exist.
I've rafactored you code to avert it.
Another problem is that you uses dot in removeClass('.before') while it should be removeClass('before') - without dot
$(function() {
var navlogo = $('.nav-logo');
$(window).scroll(function() {
var scroll = $(window).scrollTop();
if (scroll >= 1) {
navlogo.removeClass('before').addClass('after');
} else {
navlogo.removeClass('after').addClass('before');
}
});
});
body {
background-color: rgba(0,0,0,.2);
}
.space {
padding: 300px;
}
.before {
content: url(https://image.ibb.co/kYANyv/logo_test_before.png)
}
.after {
content: url(https://image.ibb.co/jYzFJv/logo_test_after.png)
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div>
<img class="nav-logo before">
</div>
<div class="space">
</div>
I have been trying to make a fullscreen menu toggle with smooth fade in and out.
That works fine with all elements except for svg elements and picture.
Basically when you click on the menu burger you can clearly see the image and the svg icons not fading out, everything else does, but not these elements.
At first I thought it could be a z-index related problem and changed the numbers around but nothing worked.
I have been trying to find information about this for days but can't find anything on it.
I would highly appreciate your help on this, thank you.
Heres my fiddle
And code:
(function () {
"use strict";
var toggles = document.querySelectorAll(".c-hamburger");
for (var i = toggles.length - 1; i >= 0; i--) {
var toggle = toggles[i];
toggleHandler(toggle);
};
function toggleHandler(toggle) {
toggle.addEventListener("click", function (e) {
e.preventDefault();
(this.classList.contains("is-active") === true) ? this.classList.remove("is-active") || $("#testMenu").fadeOut(300) : this.classList.add("is-active") || $("#testMenu").fadeIn(300);
});
}
})();
It happens because menu container is listed before image in source so it has lower index in z axis. Make #testMenu on the top with z-index and position:relative
<nav id="testMenu" style="display: none; z-index: 1000; position: relative;">
To control the z-index, the element must have a stacking order, like declaring a position.
When you introduce the position property into the mix, any positioned elements (and their children) are displayed in front of any non-positioned elements.
Try for example, changing this:
#contact {
z-index: -1;
position: relative;
...
}
Here is the working fiddle
Please take a look at this basic example:
http://tympanus.net/Blueprints/ResponsiveFullWidthGrid/
Now imagine that by clicking a cat box, I would need (especially on small to medium screens) to add a 100%-width text box (say a description of the clicked cat) below the clicked cat's row. That text box should push down the rest of the rows.
I am full css/js/frontend developer but I never faced a problem like this. It's also the first time I'm going to use a flexbox layout. With a fixed layout would be quite trivial, but in this case I cannot figure out a good way of doing it. One of the things to solve for example is: where should I put the box (relative to the clicked box?), and should I change position via javascript based on current items-per-row or maybe there a smarter css way?
Any idea is appreciated.
That was an interesting challenge :)
The only way to know where to place the expanded area (I called it infoBox), is to identify the 1st node of the next line, and then insert it before it. If there is no node on the last line, we can append it to the end of the ul.
I've also added a window.resize event handler that will close the infoBox, so it won't break the responsive layout, and a close button.
Working example - fiddle.
HTML was copy paste from the codrop article.
JS
var rfgrid = document.querySelector('.cbp-rfgrid');
var items = Array.from(document.querySelectorAll('.cbp-rfgrid > li'));
/** Create infoBox **/
var infoBox = document.createElement('div');
infoBox.classList.add('infoBox');
infoBox.innerHTML = '<div class="close">X</div><div class="content"></div>';
infoBoxClose = infoBox.querySelector('.close');
infoBoxContent = infoBox.querySelector('.content');
/** add close button functionality **/
infoBoxClose.addEventListener('click', function() {
rfgrid.removeChild(infoBox);
});
/** remove infoBox on resize to maintain layout flow **/
window.addEventListener('resize', function() {
rfgrid.removeChild(infoBox);
});
items.forEach(function (item) {
item.addEventListener('click', function (event) {
event.preventDefault();
var insertReference = findReference(this); // get refence to next line 1st node
infoBoxContent.innerHTML = items.indexOf(this); // example of changing infoBox content
if(insertReference) {
rfgrid.insertBefore(infoBox, insertReference); // insert infoBox before the reference
} else {
rfgrid.appendChild(infoBox); // insert infoBox as last child
};
});
});
/** find reference to 1st item of next line or null if last line **/
function findReference(currentNode) {
var originalTop = currentNode.offsetTop; // get the clicked item offsetTop
do {
currentNode = currentNode.nextSibling; // get next sibling
} while (currentNode !== null && (currentNode.nodeType !== 1 || currentNode.offsetTop === originalTop)); // keep iterating until null (last line) or a node with a different offsetTop (next line)
return currentNode;
}
CSS (in addition to the original)
.infoBox {
position: relative;
width: 100%;
height: 200px;
padding: 20px 0 0 0;
clear: both;
background: paleturquoise;
}
.infoBox > .close {
position: absolute;
top: 5px;
right: 5px;
cursor: pointer;
}