Search code examples
javascriptjqueryjquery-animatesmooth-scrolling

Scroll animation behaves erratically going up and down, possible input lag on mousewheel


I'm trying to trigger a smooth scroll to an element at the end of the page and another one to the top of the page everytime I move the mousewheel respectively down or up. The two parts have both height:100vh.

The thing is that once it goes down it starts to behave randomly. I feel like I need to interrupt the animation completely after the scroll is completed because it "fights with itself" struggling to go back up and vice versa. Of course I could be easiy wrong, I'm trying to learn the way.

Is there some performance issue? Maybe it is unable to get the inputs in time? Something is overlapping? It seems like there's some sort of cooldown before I can scroll again. This is what I'm trying to understand. Thanks

jQuery(window).bind('mousewheel DOMMouseScroll', function(event){
    if (event.originalEvent.wheelDelta > 0 || event.originalEvent.detail < 0) {
        // scroll up
        console.log("scroll up");
        jQuery('html,body').animate({scrollTop: jQuery("#top").offset().top}, 1200, 'linear');
    }
    else {
        // scroll down
        console.log("scroll down");
        jQuery('html,body').animate({scrollTop: jQuery("#bottom").offset().top}, 1200, 'linear');
    }
});

Same thing with this, here I'm using the Jquery.scrollTo library

jQuery(window).bind('mousewheel DOMMouseScroll', function(event){
    if (event.originalEvent.wheelDelta > 0 || event.originalEvent.detail < 0) {
        // scroll up
        console.log("scroll up");
        jQuery('body').scrollTo('#top', {duration:1200});
    }
    else {
        // scroll down
        console.log("scroll down");
        jQuery('body').scrollTo('#bottom', {duration:1200});
    }
});

Here's the html for completeness:

<div id="top" style="height:100vh;background-color: #2196f3;"></div>
<div id="bottom" style="height:100vh;background-color: #009688;"></div>

EDIT: If I move the mousewheel just the bare minimum it works perfectly both ways so the problem is input overlapping, in other words I need a way to send just the first scroll input and not the entire scroll otherwise too many inputs make the script "crash".

here's a working example, try to scroll up and down: https://jsfiddle.net/mr8hnxbd/


Solution

  • Calling .stop(true) works, but I believe it might cause issues mid animation if you keep scrolling in the direction of the animation, extending the duration of the animation. Alternatively you can do the following to ensure the animation completes before doing another animation.

    (function(jQuery) {
            let position = '';
            let scrolling = false;
            let animationDuration = 1200;
            jQuery(window).bind('mousewheel', function(event){
                if (event.originalEvent.wheelDelta > 0) {
                    // scroll up
                    if (scrolling || position === 'top')
                        return;
    
                    //console.log("scroll up");
                    scrolling = true; //Prevents any scrolling when scrolling is active
                    position = 'top'; //Prevents scrolling up when already at the top
                    jQuery('html,body').animate(
                        {
                            scrollTop: jQuery("#top").offset().top
                        },
                        animationDuration,
                        'linear',
                        function () {
                            scrolling = false; //After the animation is complete, set scroling to false
                        },
                    );
                }
                else {
                    // scroll down
    
                    if (scrolling || position === 'bottom')
                        return;
    
                    //console.log("scroll down");
                    scrolling = true;
                    position = 'bottom';
                    jQuery('html,body').animate(
                        {
                            scrollTop: jQuery("#bottom").offset().top
                        },
                        animationDuration,
                        'linear',
                        function () {
                            scrolling = false;
                        },
                    );
                }
            });
        })($);
    <div id="top" style="height:100vh;background-color: #2196f3;"></div>
    <div id="bottom" style="height:100vh;background-color: #009688;"></div>
    
    <script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>