Search code examples
javascriptwebapirequestanimationframe

Nested requestAnimationFrame calls - order of repaints and callback invocations


I am trying to understand how requestAnimationFrame works, so that I know exactly what behavior to expect when I use it. From what I understand, when I call requestAnimationFrame, it "tells" the browser that I want to call the function I passed to it as an argument sometime before the next repaint. I expect that this function will definitely be called before the next repaint, whenever that is. I do not expect that the repaint will necessarily happen immediately after my function is called.

Say I have something like:

requestAnimationFrame(() => {
    do_1st_thing();
    requestAnimationFrame(() => {
        do_2nd_thing();
        requestAnimationFrame(() => {
            do_3rd_thing();
            requestAnimationFrame(() => {
                do_4th_thing();
                requestAnimationFrame(() => {
                    do_5th_thing();
                })
            })
        })
    })
});

I assume that generally something like this will happen, in this order:

1st thing
repaint
2nd thing
repaint
3rd thing
repaint
4th thing 
repaint
5th thing
repaint 

I assume something like this could also happen:

1st thing
2nd thing
repaint
3rd thing
4th thing 
repaint
5th thing
repaint 

But generally I expect that there will be repaints between thing 1, thing 2, thing 3 and so on.

Where it gets confusing for me is that I don't know why this is the case. Based on my understanding so far, it "should" go something like this: the browser is about to do a repaint. At some point the outermost rAF call happened, so it knows that it needs to do thing 1 AND call rAF again before it can do the repaint. So, it does thing 1, calls rAF, at which point it is told that before the next repaint, there is more stuff to be done (thing 2, and another rAF call). Now, does it decide to go ahead with the repaint and...queue thing 2 & the next rAF call for before the repaint after the one it was about to do, or does it ...just do them immediately*? I assume not, but I'm not sure why, other than it would be silly.

My second question is, assuming there's repaints between thing 1, thing 2, thing 3 and so on: Is there any guarantee that there will always be a repaint between them?

*ultimately leading to this order:

thing 1
thing 2
thing 3
thing 4
thing 5
repaint

Solution

  • The browser has to maintain an ordered map of animation frame callbacks.
    When you call requestAnimationFrame(cb), cb is queued in that map.
    In the rendering frame, which is a special iteration of the event-loop where the browser will run until the end of the "update the rendering" steps, the browser will run a few event callbacks like resize, or scroll, etc., and then it will run the animation frame callbacks. This algorithm basically ask to grab all the keys currently in the animation frame callbacks queue, and iterate over that list of keys and callbacks without looking back at the map. So if new callbacks are added during this algorithm, they won't be called right now, but only the next time this algorithm is executed.

    In pseudo code that would give

    while(true) {
      // Do all the thing the event loop has to do, like pick a task to run
      ...
      if (now() === time_to_render) { // "rendering frame"
        runAllTheRenderRelatedEventCallbacks();
        // run the animation frame callbacks
        let keys = getKeys(animationFrameCallbacksMap);
        for(let key of keys) {
          let callback = animationFrameCallbacksMap[key];
          delete animationFrameCallbacksMap[key]; // remove from the map
          call(callback); // Even if here we add a new rAF callback to the map,
                          // its key won't be in 'keys' yet
        }
        doOtherStuffLikeRunTheResizeObserverCallbacks();
        paint();
      }
    }