React|Essential of Language and Web

Browser Rendering Principles: Understanding Reflow and Repaint is Key to Optimization

0
Browser Rendering Principles: Understanding Reflow and Repaint is Key to Optimization

Chris's ambitious animated menu feels sluggish for some reason.

"It's a brand new MacBook, so why is it stuttering like this?"

Opening the code, he found that he was increasing the width value by 1px repeatedly using JavaScript to open the menu. Chris had effectively just forced the browser into extreme overwork.

No matter how efficient React's Virtual DOM is, if you don't understand the principle of how the browser actually draws the screen, your performance optimization will only be half-complete. Today, we dig into the process of the browser stamping pixels onto the screen: the Critical Rendering Path.

The Browser's Factory Line: The Rendering Pipeline

From receiving an HTML file to displaying it on the screen, the browser goes through a specific assembly line.

  • DOM Tree Generation: Parses HTML to create the DOM Tree, representing relationships between tags.
  • CSSOM Tree Generation: Parses CSS files to create the CSSOM Tree, representing style rules.
  • Render Tree Combination: Combines the DOM and CSSOM to filter out only the elements that will actually be drawn on the screen (elements with display: none are excluded here).
  • Layout (Reflow) 💥: Calculates exactly where and at what size each element will be placed on the screen. (This process is what we call Reflow.)
  • Paint (Repaint): Fills in colors, writes text, and draws images at the calculated positions. (Repaint)
  • Composite: Stacks the elements drawn on multiple layers in order to create the final screen.
  • The core of performance optimization depends on how much you can reduce steps 4 (Layout) and 5 (Paint).

    Reflow and Repaint: The Main Culprits of Performance Costs

    1. Reflow (Layout): "Calculate it again!"

    This is the most expensive operation. If an element's geometric information (width, height, position) changes, the browser must recalculate the layout not just for that element, but also for its affected children, parents, and potentially the entire document.

    It's like moving a sofa in the living room by 1cm, and then having to measure the position of every other piece of furniture in the house all over again with a ruler.

    Properties that trigger Reflow:

  • width, height, padding, margin, border
  • font-size, font-weight
  • position (relative, absolute, etc.)
  • Window Resizing
  • 2. Repaint (Paint): "Just recolor it!"

    This is cheaper than Reflow but still incurs a cost. It occurs when the layout (position, size) doesn't change, but the visible style does.

    Properties that trigger Repaint:

  • color, background-color
  • visibility (Note: display: none triggers Reflow)
  • box-shadow
  • Important: If a Reflow occurs, a Repaint always occurs subsequently. If the position changes, the browser obviously has to draw it again.

    Optimization Strategy: Leverage the GPU (Composite)

    So, how do we make animations smooth (60fps)?

    The answer is to skip the Layout and Paint stages entirely.

    Browsers have properties that only go through the Composite stage. These are processed by the GPU, not the CPU, making them extremely fast.

    ❌ Bad Example: Torturing the CPU

    This is the code Chris wrote. Changing the left property causes a Reflow every single frame.

    /* style.css */
    .box {
      position: absolute;
      left: 10px;
      transition: left 0.5s;
    }
    
    .box.move {
      /* Layout(Reflow) Triggered! -> Paint Triggered! -> Slow */
      left: 100px; 
    }

    ✅ Good Example: Offloading to the GPU

    transform and opacity skip the layout and paint stages and jump straight to the Composite stage.

    /* style.css */
    .box {
      /* Separated into its own layer for GPU acceleration */
      transform: translateX(10px);
      transition: transform 0.5s;
    }
    
    .box.move {
      /* Only Composite Triggered! -> Fast */
      transform: translateX(100px);
    }

    Forced Synchronous Layout (Layout Thrashing)

    A single line of JavaScript can ruin performance. Browsers normally optimize rendering by batching changes and processing them at once. However, if we try to read a specific layout value, the browser is forced to clear the queue and calculate the layout immediately to give you the accurate value.

    // layout-thrashing.js
    const box = document.getElementById('box');
    
    // 1. Write Style
    box.style.width = '100px';
    
    // 2. Read Style -> 💥 Forced Layout!
    // Browser: "Wait, you changed the width, so to give you the accurate clientWidth, I have to calculate (Reflow) right now."
    console.log(box.clientWidth); 
    
    // 3. Write Again
    box.style.height = '100px';

    To prevent this, avoid mixing Reads and Writes. Perform all reads first, then writes, or utilize requestAnimationFrame.

    Key Takeaways

  • Reflow (Layout): Occurs when the size or position of an element changes. It is the most expensive. (width, top, left)
  • Repaint (Paint): Occurs when only colors or styles change. (color, background)
  • Composite: transform and opacity skip layout and paint, utilizing the GPU. Always use these for animations.
  • Optimization Tip: When creating animations, turn on Paint Flashing in the 'Rendering' tab of Chrome DevTools and visually check how much of the green area is being redrawn.


    Now that we understand how the browser draws pictures, it's time to learn how to reduce network costs. How can we ensure users don't have to download an image again after they've received it once?

    Continuing in: "The Complete Guide to HTTP Caching: Cutting Network Costs with Cache-Control and ETag"

    🔗 References

  • Google Developers - Critical Rendering Path
  • Reflow vs Repaint
  • CSS Triggers (Check rendering cost by property)