requestAnimationFrame Scheduling For Nerds
A few people were curious about the scheduling of requestAnimationFrame callbacks, so here’s the tl;dr:
All rAF callbacks always run in the same or next frame of work
Any rAFs queued in your event handlers will be executed in the same frame.
Any rAFs queued in a rAF will be executed in the next frame. (Same for any queued within IntersectionObserver or ResizeObserver callbacks.)
All rAFs that are queued will be executed in the intended frame.
Every rAF you request will run
rAFs aren’t throttled for you if they take too long to execute.
Let’s say there’s 5 callbacks queued up, and they’ll each take ~100 ms. The browser will not distribute them amongst many frames. It’ll try to run them all in the intended frame, even if it takes 500 ms. That means a pretty significant jank.
You may be asking, “why would there be FIVE animationFrame callbacks requested from within a single frame?” It can accidentally happen if you do 2 very reasonable things. 1) You request a new callback at the end of a rAF. 2) You request rAF callbacks from a input handlers. As a result, you’ve doubled/tripled your work each frame. Youch.
It’s up to you to coalesce rAFs on your own. So: if there’s a chance of multiple of the “same” callbacks firing within the same frame, then you must manage the scheduling/coalescing.
That said, Chrome (at least) will try to help some. If rAFs are hogging the main thread, the browser will start to throttle input events so that, hopefully, the contention will clear up.
And now this.. Two diagrams that help illustrate the lifecycle of events in a frame:
(Update Feb 2018. Jake Archibald recently gave a talk on the event loop which will almost definitely tickle your fancy:)
Addendum about the rAF timestamp
(added Feb 2018)
Many folks express confusion about the timestamp included as the first argument of the rAF callback. And for good reason.
The timestamp represents the beginning of the main thread frame that we’re currently in. This means that multiple different rAF callbacks can get back the exact same timestamp:
If that’s not very useful to you (you’re not alone), then feel free to grab performance.now() at the start of the callback and use that for your purposes.