Fading in/out scroller animation - javascript

I have a slider that slides from right to left as the user scrolls down the page.
I want the slider to fade out when the last slide is out of view when the user scrolls down, and I want the slider to fade in when the last slide is in view when the user scrolls up.
An example of what I want can be seen here
I have the slider working fine but am having trouble with the fade out/fade in animation.
My idea was to first store an opacity value of 1 in a useRef
const countRefOpacity = useRef(1);
And add a wheel event to my window
useEffect(() => {
window.addEventListener("wheel", handleWheel);
return () => {
window.removeEventListener("wheel", handleWheel);
};
}, []);
My handleWheel function decreases my slider opacity when the user scrolls down a certain point and increases the opacity when the user scrolls up a certain point
const handleWheel = (e) => {
if (window.scrollY >= pan.current?.offsetTop * 2 && e.deltaY > 0) {
countRefOpacity.current = countRefOpacity.current - 0.007;
slider.current.style.opacity = countRefOpacity.current;
} else if (window.scrollY < pan.current?.offsetTop * 2 && e.deltaY < 0) {
countRefOpacity.current = countRefOpacity.current + 0.007;
slider.current.style.opacity = countRefOpacity.current;
}
};
I can see the fade out animation working when the user scrolls down, but the fade in animation is pretty slow when the user scrolls up.
How can I revise my code so I can make the fade in/fade out animation smooth and natural?
Codesandbox Link

If you want a CSS property to change smoothly, you can use the transition property, e.g. in your case
transition: opacity .5s;
So, assuming I understood your question correctly, I would probably use the transition property as well as add an event listener to the window's scroll event that checks whether the user has scrolled past a certain point and updates the opacity accordingly, i.e. sets it to 1 if it's before that point and to 0 otherwise. There's no need for you to calculate a delta since the transition property will handle the fade-in/fade-out for you.
Also, I believe you probably want to use scroll instead of wheel, since I think wheel only fires when you use a mouse wheel and doesn't apply to other types of scrolling.

Related

Show navigation in React when page is scrolled to certain component

I am trying to achieve a certain effect with a navbar. The challenges I am facing are as below:
The navigation should appear only when the user scrolls to a certain component on the website and it should work the other way around when scrolling up
There should be a progress bar in the navigation on the scroll
I could only find how to make that starting from the top of the website but not starting from a certain point on the website
I don't need the code, just an idea of how to approach this. Maybe there are some good libraries that could help.
Thanks!
In vanilla javascript we can check the body scroll height and get it like this:
var scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
After you get the scrollTop value you can just make an if statement and display navbar when scroll gets to your desired value using css properties such as display, opacity or even height. You can even add the transition on these properties in css:
.navbar: {
transition: height 1s, opacity 1s;
}
For the progress bar you can just make a div inside the navbar, that scales with the scrollTop variable. You can calculate at what percentage progress bar should be like this:
addEventListener("scroll", event => {
var scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
var height = document.documentElement.scrollHeight - document.documentElement.clientHeight;
var progressPercent = (scrollTop / height) * 100
console.log(progressPercent + "%");
});
You need event listener to update the value every time you use the scroll. In react you can do this in the useEffect() hook, but don't forget to remove it in a cleanup function, return part of useEffect() or you will end up with more event listeners every time you use the scroll and kill the page performance as the number of them increases. Read more about this in this article.
Perhaps you're looking for React's onMouseEnter and onMouseLeave events on your component to show/hide your navbar and start your progress bar
<Component onMouseEnter={() => {showNavBar()}} />
Events Documentation

Navbar which hides when you scroll down and appears again when you scroll up

I have explored few approaches to this, but none really seems to work exactly like I would like:
I would like that when scrolling down, navbar is moving up at the speed the user is scrolling down, like that is static at that point.
When it disappears, I would like that the bottom of it is still visible, because this is where I have a progress bar (but maybe progress bar should detach at that point and be on top of the viewport).
When you scroll up, I would like that navbar appears again, again at the speed of scrolling, like it is static, until you see the whole navbar, when it should stick to the top of the viewport.
Here is an example of behavior I would like, but not performance/experience (because behavior is implemented using scroll event, it is not smooth).
I have also attempted to use CSS transform, which would on scroll down add a class to hide the navbar, and scroll up remove the class, animating the navbar hiding/showing, but the issue with that is that animation speed is disconnected with scrolling speed.
I tried CSS sticky position as well, but it looks like I need the opposite of what it provides.
Is there some other way to make this work well?
I've looked at your problem and I think i found a simple approach.
with this simple function you can get the amount of pixels user has scrolled.
window.onscroll = function (e) {
console.log(window.scrollY); // Value of scroll Y in px
};
after user scrolls the desired amount of pixels, make the progress bar fixed top ( or position:fixed;top:0)
Checking the link you provided, it seems to work as expected (you want it to be linked to the scroll event since you want it to move as "static"). If, though, it staggers on some system due to inconsistent scroll events, you could try adding a transition property with a small enough duration. Keep in mind the this should only be enabled while the position property remains the same, otherwise when changing from "absolute" to "fixed" it would mess things up, since the coordinate origin changes.
So you can add another variable let changedPosition = false; and whenever you change the position property you can do
if (position !== "absolute") {
changedPosition = true;
} else {
changedPosition = false;
}
position = "absolute";
or
if (position !== "fixed") {
changedPosition = true;
} else {
changedPosition = false;
}
position = "fixed";
and when you apply the style do
navbar.style = `position: ${position}; top: ${navbarTop}px; transitiona: ${
changedPosition ? "none" : "100ms linear"
}`;
like https://codepen.io/gpetrioli/pen/XWVKxNG?editors=0010
You should play around a bit with the transition properties you provide, i just put some sample values there.

jQuery .stop() on scroll is blocking animation

this is my simple scroll function.
When user scrolls a certain distance from the bottom of the page, a registration box appears via animation:
$(window).scroll(function(){
if($docHeight - $top < 500 && document.cookie.indexOf("flag") < 0) {
$('.register').stop().animate({
'bottom': -1,
});
return false;
}
});
My problem is that the .stop() function before .animate() triggers every time the user scrolls; and so during animation it is constantly stopping as the user scrolls causing a 'juddering' effect (of course, no juddering if the user doesn't scroll).
I have thought about setting flags during animation and not running the animation to avoid this, but I can't figure it out.
I believe the function .once() could be used well here?
Thanks

Fix delayed jquery fade on scroll

vol7ron showed me how to achieve an effect where an element fades in on scroll down, and fades out on scroll up here: Why does "($(this).css('opacity')==.3)" not work?
jsfiddle: http://jsfiddle.net/b7qnrsrz/16/
$(window).on("load", function () {
function fade() {
$('.fade').each(function () {
/* Check the location of each desired element */
var objectBottom = $(this).offset().top + $(this).outerHeight();
var windowBottom = $(window).scrollTop() + $(window).innerHeight();
/* If the object is completely visible in the window, fade it in */
if (objectBottom < windowBottom) { //object comes into view (scrolling down)
if ($(this).css('opacity') != 1) {
$(this).stop(true,false).fadeTo(500, 1);
}
} else { //object goes out of view (scrolling up)
if ($(this).css('opacity') == 1) {
$(this).stop(true,false).fadeTo(500, .3);
}
}
});
}
fade(); //Fade in completely visible elements during page-load
$(window).scroll(function () {
fade();
}); //Fade in elements during scroll
});
This works almost perfectly. As is, the boxes wait to fade until I've stopped scrolling. So if I smoothly scroll past three of them, rather than fading one-by-one as I scroll past each one, they all wait to fade in until after I stop scrolling. How can I remedy this so that fade triggers even while scrolling, not waiting until after stopping?
That's because there is a bug in the script.
Every time you scroll it executes the fade function. Because the animation is prefixed with a stop() the animation never really gets any time to run if you scroll slowly.
You can either remove the .stop() or find a way to only apply the fade-in to elements that are not already fading in.
There seems to be another bug. Sometimes items are not detected as being in the viewport and they will not fade in. I'm not sure why that happens. It might have something to do with scrolling really fast.

Flickering with jQuery animate scrollTop

My problem
I am making a vertical website for a client who wishes to have the window "snap" to the nearest page when most of the element is visible in the viewport. So, if the page is 85% visible, it should scroll to be 100% visible.
My problem is that occasionally when scrolling all the way to the top or bottom of the viewport, the viewport will "stick" to the first or last element, preventing a few scroll events and causing a highly noticeable flicker.
A working fiddle is here: http://jsfiddle.net/RTzu8/1/
To reproduce the error, use the scrollbar to scroll to the bottom of the page. Then, scroll up with your mousewheel. You should see the flicker. Sometimes it takes a few refreshes or attempts, but the issue is highly reproducible.
I'm at a loss as to what could be causing this issue. See below for a run-down of my code and what I have tried to prevent it so far.
My code
To accomplish my snapping, I needed to detect whether an element was a certain percentage visible. So, I added a jQuery function, isNearScreen, below. I have thoroughly tested this function, and as far as I can tell it returns accurate results.
//Modification of http://upshots.org/javascript/jquery-test-if-element-is-in-viewport-visible-on-screen
//Returns "true" if element is percent visible within the viewport
$.fn.isNearScreen = function(percent){
var offset = 1 - percent;
var win = $(window);
var viewport = {
top : win.scrollTop()
};
viewport.bottom = viewport.top + win.height();
var bounds = this.offset();
bounds.bottom = bounds.top + this.outerHeight();
bounds.top = bounds.top;
//If the element is visible
if(!(viewport.bottom < bounds.top || viewport.top > bounds.bottom)){
//Get the percentage of the element that's visible
var percentage = (viewport.bottom - bounds.top) / this.height();
//If it's greater than percent, but less than 1 + (1 - percent), return true;
return (percentage > (1 - offset) && percentage < (1 + offset));
}
return false;
};
I then created a snap function, which makes use of Underscore.js's _.debounce function, to only fire on the trailing end of continuous scroll events. It fires after a 500ms timeout, and I am fairly (though not 100%) convinced that it is firing correctly. I have not been able to reproduce console logs that would indicate multiple concurrent firings.
//Snaps the screen to a page after scroll input has stopped arriving.
var snap = _.debounce(function(event){
//Check each page view
$.each($('.page-contents'), function(index, element){
//If the page view is 70% of the screen and we are allowed to snap, snap into view
if($(element).isNearScreen(0.7)){
$('html,body').animate({
scrollTop: $(element).offset().top
}, 300);
}
});
}, 500);
Finally, I bind to the window's scroll event
$(window).on('scroll', snap});
The (extremely simplified) HTML:
<div class="page">
<div class="page-contents"></div>
</div>
<div class="page">
<div class="page-contents"></div>
</div>
<div class="page">
<div class="page-contents"></div>
</div>
<div class="page">
<div class="page-contents"></div>
</div>
and CSS:
.page{
height: 750px;
width: 100%;
margin: 10px 0;
background: gray;
}
.page-contents{
height: 100%;
width: 100%;
}
What I've tried
I have tried the following, with no success:
Setting a boolean, 'preventSnap', on the window, checking its state, and only firing the animate portion of snap if it is set to false. After animation, set it to true, then set it to false after 500ms (which should in theory prevent double firings).
Calling .stop() on the element before running the snap animation.
Calling event.preventDefault() on the scroll event before running the animation.
Reducing and increasing my _.debounce delay. Interestingly, a lower _.debounce delay (200-300ms) seems to aggravate the problem and a higher _.debounce delay (1000ms) seems to fix it. This is not an acceptable solution, however, as it feels "long" waiting 1sec for the page to "snap".
Changing the heights of the elements
If there is any other information I can provide, please let me know. I'm at a loss!
I think this is a combination of events and how _.debounce works. I noticed in the fiddle (in Chrome) that the elements were 'jitterring' long after the snap finished. If you put a console log in the snap event handler you can see it's constantly being called after a snap even with no scroll inputs.
This must be the scroll animation itself setting off the snap, I tried to set a flag to prevent dual snapping and clearing the flag after the animation was finished -- however that didn't work I think because _.debounce is queuing the event to happen later (after the animation finishes and clears the flag).
So what does work is to add this as the start of the snap handler:
var nosnap = false;
var snap = _.debounce(function(event){
// Don't snap if already animating ...
if (nosnap) { nosnap = false; return; }
nosnap = true;
Fiddle
That prevents the animation directly firing the next snap event -- however that's going to cause issues if you scroll again during the animation.
So, that's a bit of a hack. Ideally you want to be able to tell what's causing the scroll event and react accordingly but there's no easy way to do that.
I absolutely think you need to stop the animation when handling a second scroll event as well.

Categories

Resources