javascriptdomrepaintrequestanimationframereflow

What happens if we manipulate DOM in requestAnimationFrame?


My understanding is whenever there is some DOM manipulation like inserting a DOM element would trigger a reflow and most likely followed by a repaint. Please correct me if I'm wrong. Quoting the MDN Web Docs,

The window.requestAnimationFrame() method tells the browser that you wish to perform an animation and requests that the browser calls a specified function to update an animation before the next repaint

the requestAnimationFrame (a.k.a. aAF) callback is called just before the browser is about to repaint. So does this mean if we somehow manage to do a DOM manipulation inside this rAF (edit: and also queue another rAF at the end) which triggers a reflow everytime and thus a repaint, we would be stuck in an infinite loop without actually rendering anything on the screen.

Or is it the case that once the browser had decided to do a repaint, it will stick with it and apply any updates that happened in the RAF callback in the next repaint?


Solution

  • whenever there is some DOM manipulation like inserting a DOM element would trigger a reflow and most likely followed by a repaint

    The painting action occurs asynchronously, so "trigger" should be understood in that way. First your JavaScript code will finish before that actually happens.

    if we somehow manage to do a DOM manipulation inside this rAF (edit: and also queue another rAF at the end) which triggers a reflow everytime and thus a repaint, we would be stuck in an infinite loop without actually rendering anything on the screen.

    The needs for repaint accumulate and are not synchronously fulfilled. First your code has to complete until the call stack is empty. So there is no infinite loop here.

    Or is it the case that once the browser had decided to do a repaint, it will stick with it and apply any updates that happened in the RAF callback in the next repaint?

    Yes. When the RAF callback is called, that code gets a final chance to make updates to the DOM, which may accumulate further the needs for painting. If in that callback you also register another callback on RAF, it will not execute at that time, but later: at the next time that the browser will prepare its repaint task -- so not the current one.

    Simplified example

    Let's say you have this code:

    requestAnimationFrame(update);
    
    myElement.style.backgroundColor = "silver"; // This queues a need for repaint
    
    function update() {
        // This queues a need for repaint
        myElement.style.width = Math.floor(Math.random() * 100) + "px";
        requestAnimationFrame(update);
    }
    

    When this executes, we get the following sequence:

    1. update is registered as callback
    2. The background change schedules a need for repainting
    3. The callstack becomes empty
    4. The browser starts its repaint job, but takes into account there is a registered callback. So it removes this registration (because it should only run once) and executes update before doing anything else.
    5. The width change schedules a need for repainting. The list of changes now includes the background change and this width change and any cascade effect that has been calculated. (How this is represented is browser dependent)
    6. The update function is registered as callback again.
    7. The browser now checks what it needs to do as part of this repaint job, and performs all that is needed to visualise the effects of the background and width changes.
    8. The paint job ends. All that is left is the registered update callback.
    9. When the browser performs its next paint cycle, we start again from step 4, but now there is no queued background-change any more. For the rest it will be the same process.