Bug Tracker

Opened 5 years ago

Closed 5 years ago

Last modified 5 years ago

#14820 closed bug (invalid)

scroll event fires after scrollTop animation is complete

Reported by: zzzzbov@… Owned by: zzzzbov@…
Priority: undecided Milestone: None
Component: unfiled Version: 2.1.0
Keywords: Cc:
Blocked by: Blocking:

Description

Animations that animate the scrollTop property trigger scroll events, however it appears that an additional scroll event is triggered after the animation's complete callback has been called.

http://jsfiddle.net/LMR4d/

I'm experiencing this in Chrome, Firefox, and IE10 on Windows 7.

Change History (10)

comment:1 Changed 5 years ago by voelzmo@…

Does animate() really return a deferred? If I try your example with a self-created Deferred and the 'complete' callback, it seems to work:

function animate(){
  var deferred = new jQuery.Deferred();
  $('html, body').animate({
    scrollTop: 500
  }, {
    complete : function() {
        deferred.resolve();
    },
    duration : 3000
  });  
  return deferred;
}

comment:2 in reply to:  1 ; Changed 5 years ago by anonymous

Replying to voelzmo@…: Argh, nevermind. I was of course forgetting the most important thing: setting animatingScroll = false in the complete callback. Then I see the exact same behavior, see http://jsfiddle.net/89pnX/. PS: We're on jQuery 1.7.2 so this problem seems to have been around for a while.

comment:3 in reply to:  2 Changed 5 years ago by voelzmo

Replying to anonymous: Aaaaand here is the correct URL, sorry for the mess: http://jsfiddle.net/89pnX/3/

A possible workaround seems to be to delay the execution of the complete callback by a magic number: http://jsfiddle.net/89pnX/4/

Last edited 5 years ago by voelzmo (previous) (diff)

comment:4 in reply to:  1 Changed 5 years ago by zzzzbov@…

Replying to voelzmo@…:

Does animate() really return a deferred? If I try your example with a self-created Deferred and the 'complete' callback, it seems to work:

function animate(){
  var deferred = new jQuery.Deferred();
  $('html, body').animate({
    scrollTop: 500
  }, {
    complete : function() {
        deferred.resolve();
    },
    duration : 3000
  });  
  return deferred;
}

animate() doesn't return a deferred, promise() on a jQuery collection returns a deferred that resolves when all 'fx' queues have completed. The provided example code resolves when the first 'fx' queue has completed.

comment:5 Changed 5 years ago by zzzzbov@…

It appears as though this isn't actually a bug with jQuery, and instead seems to be a quirk of scroll events in the browser. See this related question on StackOverflow for additional details.

Assigning a new scroll value does not immediately queue a scroll event. Instead, there can be an unknown amount of delay between setting scrollTop and the scroll event firing (http://jsfiddle.net/jSBn2/). Sometimes the scroll event will be immediately queued in the event loop, sometimes not.

This delay is what's causing the odd behavior of the scroll event after the complete callback.

comment:6 Changed 5 years ago by dmethvin

I thought this was what happened: When you set scrollTop it queues an event to occur once the thread completes. On the final step of the animation the Deferred resolves synchronously and immediately calls all the callbacks. Then the thread completes and the event fires.

comment:7 in reply to:  6 Changed 5 years ago by zzzzbov@…

Replying to dmethvin:

I thought this was what happened: When you set scrollTop it queues an event to occur once the thread completes. On the final step of the animation the Deferred resolves synchronously and immediately calls all the callbacks. Then the thread completes and the event fires.

Promises aren't supposed to resolve synchronously, although I'm aware that jQuery's deferreds deviate from the Promises/A+ spec. Even if that were the case, including a delay(0) call into the chain before the promise should fix the issue, which it doesn't.

comment:8 Changed 5 years ago by dmethvin

Owner: set to zzzzbov@…
Status: newpending

Oh, I see you were animating two things and then using the per-element callback. That's not going to work consistently anyway. See the discussion here: https://github.com/jquery/api.jquery.com/issues/417

Does that docs issue answer your question?

comment:9 Changed 5 years ago by trac-o-bot

Resolution: invalid
Status: pendingclosed

Because we get so many tickets, we often need to return them to the initial reporter for more information. If that person does not reply within 14 days, the ticket will automatically be closed, and that has happened in this case. If you still are interested in pursuing this issue, feel free to add a comment with the requested information and we will be happy to reopen the ticket if it is still valid. Thanks!

comment:10 in reply to:  8 Changed 5 years ago by zzzzbov@…

Replying to dmethvin:

Oh, I see you were animating two things and then using the per-element callback.

I'm not sure I follow. I animated two things and set up a promise to fire once when both fx queues were clear.

voelzmo's snippet uses the per-animation callback, maybe that's what you were referring to.

Either way, I found that the scroll event doesn't fire synchronously after scrollTop is changed, which is what is causing the erratic behavior. As it's a native browser behavior, it's not a bug in jQuery, so this ticket can remain closed.

Note: See TracTickets for help on using tickets.