4

I realized something odd about the javascript on scroll event.

Until now I was always convinced that it would be fired directly, whenever the scroll position changes.
And because javascript is blocking, a callback would always be the first thing to be executed and the first function to "see" this new value – or so I thought.

Here I have simple setup, where a global value gets updated to the current scroll position, on scroll. Then there's an interval comparing the two.
Now if you scroll around very fast it does sometimes happen, that the cached value is not the same as the actual returned scroll position.

// cache scroll pos
var scrollPos = 0;
// update on scroll
window.onscroll = function () {
    scrollPos = scrollTop();
};
// compare on interval
setInterval(function () {
    if (scrollPos != scrollTop()) {
        console.log("Out of sync!  cached:", scrollPos, "| actual:", scrollTop());
    }
}, 100);

fiddle: http://jsfiddle.net/71vdx4rv/2/
You can try scrolling around quickly or using the button. [tested on Chrome, Safari, FF, Opera]

Why is the interval function executed and knows about the 'new' scroll position, before the callback is?
Can someone please explain this to me?

Jan Paepke
  • 1,997
  • 15
  • 25
  • onscroll does not fire continuously so timer can execute in between the update.... – epascarello Sep 05 '14 at 12:26
  • No, this is not the reason. Of course the timer can execute between the updates but i thought it would always have new scroll pos values AFTER the scroll event callback. See accepted answer to learn why it does not. – Jan Paepke Sep 05 '14 at 13:04
  • Dude, my comment is the general gist of what Bergi says. onscroll !== a 1 to 1 relationship to the actual movement of the page which means it is not continuous flow up updates. yikes. – epascarello Sep 05 '14 at 13:12

1 Answers1

4

Until now I was always convinced that it would be fired directly, whenever the scroll position changes.

Unfortunately (and evidently) that's not the case. While DOM 3 does state

A user agent MUST dispatch [the scroll] event when a document view or an element has been scrolled. This event type is dispatched after the scroll has occurred.

this doesn't mean "immediately". The CSSOM draft clarifies:

[To perform a scroll, repeatedly run these steps]:

  1. Perform a [smooth or instant] scroll of box to position.
  2. Queue a task to run task, unless a task to run task is in the queue.

[where task is does fire a scroll event at the respective target]

"Queuing a task" explicitly means that the event is dispatched asynchronously, so the scroll position might already have been changed when a waiting task (like your timer) is executed before the scroll-event-firing task.

Also, when performing a smooth scroll, the browser is allowed to change the scroll position multiple times over an arbitrary timespan, before queuing the task to fire the scroll event.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375