Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scroll fires after jquery scrollTop animate completed

Why does another scroll event get called after a scrollTop animation fires its complete callback?

Click Handler:

var lock = false;

$('#id').click(function(event) {
    var pos;
    if (lock) {
        return;
    }
    lock = true;
    pos = 150;

    console.log("jump start");

    $(jQuery.browser.webkit ? "body": "html").animate({ scrollTop: pos }, 150, function () {
        lock = false;
        console.log("jump end");
    });
});

Scroll Handler:

$(window).scroll(function (e) {
    console.log("scrolling");

    if (!lock){
        alert('1');
    }
});

Log:

jump start
scrolling
jump end
scrolling

demo on jsfiddle

like image 586
vlukham Avatar asked Oct 18 '13 08:10

vlukham


1 Answers

Background

jQuery scrollTop() uses scrollTo() which is a fire and forget event. There is no stopped event for scrolling. The scroll events occur out of band from scrollTo. scrollTo means 'start scroll', a scroll event means 'scrolled (some position)'. scrollTo just initiates the starting of the scroll, it doesn't guarantee that scrolling finished when it returns. So, jQuery animation completes before final scroll (there could even be multiple scrolls backed up). The alternative would be for jQuery to wait for the position of the scroll to be what it requested (per my soln), but it does not do this

It would be nice if there was a specification that we could point to describing this, but it is just one of the Level 0 dom elements without a spec, see here. I think it makes sense the way that it works, which is why all browsers seem to implement it this way.

Why is this happening

The following occurs on the last scroll of the animation:

  1. jquery: 'Window please scroll this last bit'
  2. Window: 'I got this message from jquery to scroll I will start that now'
  3. jquery: 'woohoo I am finished the animation, I will complete'
  4. Your code: lock = false;console.log("jump end");
  5. Window: 'I have scrolled' call scroll event handlers.'
  6. Your code: $(window).scroll(function (e) { 'Why is this happening?'

As you can see jquery does not wait for the final scroll step of the animation to complete before completing the animation (going on to step 4). Partly this is because there is no stopped event for scrolling and partly this is because jquery does not wait for the scroll position to reach the position that was requested. We can detect when we have reached the destination position as described below.

Solutions

There is no stopped event for when scrolling completes. See here. It makes sense that there is no stopped event because the user could start scrolling again at any point, so there is no point where scrolling has really stopped - the user might just have paused for a fraction of a second.

User scrolling: For user scrolling, the normal approach is to wait some amount of time to see if scrolling is complete as described in the answer of the referenced question (bearing in mind that the user could start scrolling again).

scrollTop: However, since we know the position that we are scrolling to we can do better.

See this fiddle.

The crux of it is that since we know where we are scrolling to, we can store that position. When we reach that position we know that we are done.

The output is now:

jump start
scroll animation
jump end

The code is (note that this is based off your fiddle rather than the code in the edited question):

var scrollingTo = 0;
$('#id').click(function(event) {    
    if (scrollingTo) {
        return;
    }        
    console.log("jump start");
    scrollingTo = 150;
    $(jQuery.browser.webkit ? "body": "html").animate({ scrollTop: scrollingTo }, 150, function () {                
    });
});

function handleScroll()
{    
    if( scrollingTo !== 0  && $(window).scrollTop() == scrollingTo)
    {
        scrollingTo = 0;
        console.log("jump end");    
    }
}

$(window).scroll(function (e) {    
    if (!scrollingTo){
        console.log('user scroll');
    } else {
        console.log("scroll animation");
    }
    handleScroll();
});
like image 176
acarlon Avatar answered Sep 29 '22 00:09

acarlon