Understanding Browser Rendering: Reflow, Repaint, and Performance Optimizations
The article walks through the browser rendering pipeline—from parsing HTML/CSS and building the render tree to layout (reflow) and painting (repaint)—explains when these steps occur, details how browsers batch changes, and offers practical strategies such as batching DOM updates, caching layout reads, and using CSS3 hardware acceleration to minimize costly reflows and repaints for better performance.
Browser Rendering Process
This article explains the complete browser rendering pipeline, starting from parsing HTML/CSS to generating the render tree, performing layout (reflow), painting (repaint), and finally sending pixels to the GPU for display.
The process (adapted from MDN) is:
Parse HTML to create the DOM tree and parse CSS to create the CSSOM tree.
Combine DOM and CSSOM to build the Render Tree.
Layout (reflow): compute geometric information (position, size) for each visible node.
Painting (repaint): turn the render tree and layout data into actual pixel values.
Display: send the pixels to the GPU, which may compose layers before presenting on the screen.
Generating the Render Tree
The browser traverses the DOM from the root, selects only visible nodes, applies matching CSS rules, and constructs the render tree. Nodes that are not rendered (e.g., <script> , <meta> , <link> ) or are hidden with display:none are omitted.
Reflow
During reflow the browser calculates the exact position and size of each visible element based on the viewport. Example HTML used for illustration:
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Critical Path: Hello world</title>
</head>
<body>
<div style="width: 50%">
<div style="width: 50%">Hello world!</div>
</div>
</body>
</html>The outer div occupies 50% of the viewport width, and the inner div occupies 50% of its parent. During reflow the browser converts these percentages into actual pixel values.
Repaint
After reflow, repaint paints each node onto the screen using the computed geometry. Repaint always follows a reflow, but a repaint does not necessarily trigger a reflow.
When Do Reflow and Repaint Occur?
Any change that affects layout or geometry triggers a reflow, which in turn triggers a repaint. Common triggers include:
Adding or removing visible DOM elements.
Changing an element’s position.
Changing size-related properties (margin, padding, border, width, height).
Changing content (text updates, image replacement).
Initial page load.
Resizing the browser window.
Note: Reflow always forces a repaint, but repaint can happen without a full reflow.
Browser Optimization Mechanisms
Modern browsers batch DOM and style changes in a queue and execute them together to reduce layout work. However, reading layout‑related properties forces the queue to flush, causing an immediate reflow/repaint. The following properties/methods trigger a synchronous layout:
offsetTop , offsetLeft , offsetWidth , offsetHeight
scrollTop , scrollLeft , scrollWidth , scrollHeight
clientTop , clientLeft , clientWidth , clientHeight
getComputedStyle()
getBoundingClientRect()
To avoid forced layout, cache values instead of reading them repeatedly.
Minimizing Reflow and Repaint
Batching DOM Changes
Combine multiple style updates into a single operation. Example of three separate style changes that could cause three reflows:
const el = document.getElementById('test');
el.style.padding = '5px';
el.style.borderLeft = '1px';
el.style.borderRight = '2px';Better approaches:
Use cssText to set all styles at once: el.style.cssText += 'border-left:1px; border-right:2px; padding:5px;';
Toggle a CSS class: el.className += ' active';
Batch Inserting Nodes
Three techniques to reduce layout work when inserting many elements:
Detach the element from the document flow, modify it, then re‑attach.
Use a DocumentFragment to build a subtree off‑DOM and append it once.
Clone the element, modify the clone off‑DOM, and replace the original.
Example using a DocumentFragment :
const ul = document.getElementById('list');
const fragment = document.createDocumentFragment();
appendDataToElement(fragment, data);
ul.appendChild(fragment);Avoiding Synchronous Layout
Reading layout properties inside a loop forces the browser to flush the queue each iteration. Inefficient code:
for (let i = 0; i < paragraphs.length; i++) {
paragraphs[i].style.width = box.offsetWidth + 'px';
}Optimized version caches the value first:
const width = box.offsetWidth;
for (let i = 0; i < paragraphs.length; i++) {
paragraphs[i].style.width = width + 'px';
}Using CSS3 Hardware Acceleration
Properties such as transform , opacity , filters , and will-change trigger GPU acceleration, allowing animations to run without causing reflow/repaint.
Typical usage:
transform: translateZ(0);
opacity: 0.5;
filter: blur(5px);
will-change: transform;
Benefits: these animations stay on the compositor thread, keeping the main thread free for layout work.
Potential pitfalls:
Applying GPU acceleration to too many elements can increase memory usage and degrade performance.
GPU‑rendered text may lose subpixel anti‑aliasing, resulting in blurry fonts if not turned off after the animation.
Conclusion
The article covered the browser rendering pipeline, the browser’s internal optimization mechanisms, and practical techniques to reduce or avoid reflow and repaint. Understanding these concepts helps developers write more performant front‑end code.
Tencent Cloud Developer
Official Tencent Cloud community account that brings together developers, shares practical tech insights, and fosters an influential tech exchange community.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.