Day 2: Reflow and Repaint

Learn how the browser renders web pages and how to avoid performance pitfalls caused by excessive reflow and repaint operations.
Published on
|
Reading time
7 min read
FSD #2
Banner image for Day 2: Reflow and Repaint

Reflow and Repaint - Understanding Browser Rendering Performance

We've all experienced websites that feel slow and unresponsive. While many factors affect performance (network latency, resource size, JavaScript execution, etc.), today we'll focus on one critical aspect: how the browser renders content on screen.

Understanding reflow and repaint—two key processes in the browser's rendering pipeline—can help you avoid common performance pitfalls that make your user interface feel slow, even after all your resources have loaded.

The Browser's Rendering Process

Imagine you're building a house. First, you need architectural plans (HTML), then you decide on the decorations (CSS), and finally, you might install some smart home features (JavaScript). The browser builds webpages in a similar way.

Here's how the browser renders a page in five simple steps: browser rendering process
  1. Parse HTML: The browser reads your HTML and creates the DOM (Document Object Model) - a tree-like structure representing all elements on your page.

  2. Process CSS: It reads your CSS and creates the CSSOM (CSS Object Model) - another tree that defines how elements should look.

  3. Combine DOM & CSSOM: These two trees merge to form the "Render Tree" - which contains only the visible elements with their styles.

  4. Layout (Reflow): The browser calculates exactly where and how big each element should be on the screen.

  5. Paint: Finally, it fills in all the pixels - colors, images, text, and other visual elements.

Sometimes, a sixth step occurs:

  1. Compositing: The browser might divide the page into layers and handle them separately for efficiency.

What Are Reflow and Repaint?

Reflow (Layout)

👶 ELI5: Reflow is like rearranging furniture in your room. When you move your couch, you need to recalculate where everything else fits.

Technical: Reflow happens when the browser needs to recalculate the position and size of elements in the document. This is a computationally expensive process because changing one element can affect others.

Repaint

👶 ELI5: Repaint is like repainting your wall without moving any furniture. The layout stays the same, but the appearance changes.

Technical: Repaint occurs when changes are made to an element's appearance that don't affect its layout. For example, changing background-color, visibility, or outline.

When Does Reflow and Repaint Happen?

Understanding what triggers these processes helps you avoid unnecessary performance costs.

Common Reflow Triggers

Reflow occurs when you:

  1. Insert, remove, or update an element in the DOM
  2. Modify content on the page, including text changes
  3. Move a DOM element (by changing position, margins, padding, etc.)
  4. Resize the window or change the viewport
  5. Request certain element properties in JavaScript, such as:
    • offsetHeight, offsetTop, offsetWidth
    • scrollHeight, scrollTop, scrollWidth
    • clientHeight, clientTop, clientWidth
    • getComputedStyle()
    • getBoundingClientRect()
  6. Add or remove stylesheets, which causes style recalculation
  7. Change element classes that affect layout properties
  8. Calculate dimensions dependent on other elements' sizes

Remember: When an element reflows, all elements that follow it in the DOM may need to reflow as well. This is why reflow can be so costly—it can trigger a cascade of recalculations.

Common Repaint Triggers

Repaint happens when you change visual properties that don't affect layout:

  1. Background color changes
  2. Text color changes
  3. Shadows
  4. Borders (color changes, not size)
  5. Visibility (without affecting layout)
  6. Outline
  7. Opacity

These operations are generally less expensive than reflow since they don't require recalculating positions and dimensions.

Why Do These Matter? The Cost of Rendering

When the browser performs reflow and repaint operations, it uses CPU resources. This can cause:

  1. Slower page performance: Excessive reflow/repaint can make your page feel sluggish
  2. Battery drain on mobile devices
  3. Janky scrolling and choppy animations
  4. Poor user experience overall

The key thing to understand is: reflow is much more expensive than repaint. When reflow happens, it often triggers repaint afterward, creating a double performance hit.

Common Causes of Performance Problems

Let's look at some code examples that can cause unnecessary reflow and repaint operations:

Bad Practice #1: Manipulating DOM Elements in Loops

// ❌ BAD: This causes multiple reflows
function addItems(items) {
  const container = document.getElementById('container');

  items.forEach(item => {
    container.innerHTML += `<div class="item">${item}</div>`;
  });
}

Every time you modify innerHTML, the browser needs to reparse and recalculate the layout of the entire container. This happens on every loop iteration!

// ✅ GOOD: Build content off-DOM, then add it once
function addItems(items) {
  const container = document.getElementById('container');
  let content = '';

  items.forEach(item => {
    content += `<div class="item">${item}</div>`;
  });

  container.innerHTML = content;
}

Bad Practice #2: Reading Layout Properties Then Making Changes

// ❌ BAD: Forces synchronous layout
function resizeElements() {
  const elements = document.querySelectorAll('.dynamic-height');

  elements.forEach(el => {
    const height = el.offsetHeight; // This reads layout
    el.style.height = (height * 1.5) + 'px'; // This triggers reflow
    el.style.margin = (height / 10) + 'px'; // Another reflow!
  });
}

This code causes "forced synchronous layout" by reading layout properties (offsetHeight) and then making changes that require recalculation.

// ✅ GOOD: Batch reads, then batch writes
function resizeElements() {
  const elements = document.querySelectorAll('.dynamic-height');
  const heights = [];

  // Read phase
  elements.forEach((el, i) => {
    heights[i] = el.offsetHeight;
  });

  // Write phase
  elements.forEach((el, i) => {
    const height = heights[i];
    el.style.height = (height * 1.5) + 'px';
    el.style.margin = (height / 10) + 'px';
  });
}

Bad Practice #3: Inline Style Modifications

// ❌ BAD: Multiple style changes trigger multiple reflows
function animateElement(element) {
  element.style.width = '100px';
  element.style.height = '200px';
  element.style.backgroundColor = 'blue';
  element.style.padding = '20px';
  element.style.marginTop = '10px';
}

Each style change can potentially cause reflow or repaint.

// ✅ GOOD: Use CSS classes or batch changes
function animateElement(element) {
  // Option 1: Use CSS classes
  element.classList.add('animated-state');

  // Option 2: Batch style changes
  element.style.cssText = 'width: 100px; height: 200px; background-color: blue; padding: 20px; margin-top: 10px;';
}

Bad Practice #4: Not Using Layout-Friendly Properties

// ❌ BAD: These properties trigger reflow
function animateBox() {
  const box = document.getElementById('box');
  let position = 0;

  setInterval(() => {
    position += 5;
    box.style.left = position + 'px'; // Triggers reflow
    box.style.top = position + 'px'; // Triggers reflow
  }, 16);
}

Using left and top properties for animations causes reflow on every frame.

// ✅ GOOD: Use transform for better performance
function animateBox() {
  const box = document.getElementById('box');
  let position = 0;

  setInterval(() => {
    position += 5;
    box.style.transform = `translate(${position}px, ${position}px)`; // Only triggers compositing
  }, 16);
}

Advanced Tips for Optimal Rendering Performance

  1. Use requestAnimationFrame for animations to sync with the browser's render cycle
  2. Create compositing layers: If a node requires frequent repainting or reflowing, you can assign it to its own layer using will-change. This helps isolate its rendering, preventing it from impacting the performance or layout of surrounding elements.
    .moving-element {
      transform: translateZ(0);
      will-change: transform;
    }
    
  3. Debounce or throttle event handlers that might trigger layout calculations
  4. Measure then mutate: Always batch your DOM reads, then do your DOM writes
  5. Use the DevTools Performance panel to identify bottlenecks

Did you find this article helpful? Share it with your fellow developers on X or LinkedIn!


Quick Recap

Understanding reflow and repaint is essential for building high-performance web applications. By minimizing unnecessary layout recalculations, you can create smoother, more responsive user experiences that work well even on less powerful devices.

Remember these key points:

  • Reflow (layout) is expensive, repaint is less expensive
  • Batch your DOM operations
  • Read layout properties first, then make changes
  • Use CSS transforms and opacity for animations when possible
  • Test performance on lower-powered devices

What other performance optimization techniques have you found useful in your projects? Let's connect via X or Linkedin below!