Stop Layout Thrashing: Master Forced Reflows for Faster Web Pages
This article explains how browsers perform layout and reflow, distinguishes asynchronous and forced synchronous reflows, shows how render‑tree caching works, illustrates why layout thrashing hurts performance, and provides practical techniques—including batch reads/writes and React hooks—to avoid costly reflows.
What Is Layout?
Layout is the browser process that calculates each element’s size and position on a page. Depending on CSS, content, or parent elements, every element has explicit or implicit size information. Chrome, Edge, and Safari call this layout, while Firefox refers to it as reflow.
Asynchronous Layout
When a page’s styles change, the browser checks whether a layout is needed and then updates the render tree. To render a frame, the browser runs JavaScript, computes styles, and finally performs layout. This is called asynchronous layout (or asynchronous reflow).
Forced Synchronous Layout
Some JavaScript calls force the browser to perform layout immediately to obtain style or coordinate information, such as Element.getBoundingClientRect() which returns a bounding box synchronously.
const element = document.getElementById("item1");
const rect = element.getBoundingClientRect();
console.log(rect);If the element’s geometry has not been calculated yet, the browser must execute a reflow right away, a situation known as forced synchronous layout (or forced synchronous reflow).
Render Tree Caching and Incremental Updates
When a page loads, the DOM is parsed without visual styles or positions. The layout engine then computes style and geometry for each visible element and stores this information in an internal data structure called the render tree.
For a simple static HTML page:
HTML is parsed into the DOM; elements have no layout.
The browser performs an asynchronous reflow, assigning style and coordinates to each visible element.
Style and coordinates are combined into the render tree, and the style information is cached for later access.
After caching, subsequent reads of style or geometry are fast. For example, calling getBoundingClientRect() on an element after the async reflow returns the cached data.
Render Tree Cache Invalidation
Most modern web apps use client‑side JavaScript (e.g., React) to create and update visible elements. When JavaScript modifies the DOM—adding, removing, or updating nodes—the render tree may become partially or fully invalidated.
For instance, inserting a modal dialog creates elements without style or position; the browser will later perform an async reflow to compute them.
const modalRoot = document.createElement("div");
modalRoot.classList.add("modal--root");
const subDiv = document.createElement("div");
const paragraph = document.createElement("div");
// Add other DOM nodes and styles as needed...
document.body.firstChild.appendChild(modalRoot);
// DOM nodes are added!When the async reflow runs, each node receives style and position, the render tree is updated, and the new information is cached.
The browser tries to recompute only the minimal affected subtree, making reflow incremental.
Synchronous Reflow Example
The following JavaScript forces a render‑tree invalidation and then triggers a synchronous reflow:
const element = document.getElementById("modal-container");
// 1. invalidate Layout Tree
element.classList.add("width-adjust");
// 2. force a synchronous reflow. This can be SLOW!
element.getBoundingClientRect();Calling getBoundingClientRect() on a dirty element forces the browser to perform an immediate layout, extending the JavaScript task and potentially creating a long task.
Layout Thrashing
When multiple synchronous reflows are triggered within a single task or frame, the phenomenon is called layout thrashing.
const elements = [...document.querySelectorAll(".some-class")];
for (const element of elements) {
element.classList.add("width-adjust"); // invalidate Layout Tree
element.getBoundingClientRect(); // force synchronous reflow
}Repeatedly invalidating the render tree and forcing reflows can severely degrade performance, creating long tasks and lowering frame rates.
How to Avoid Layout Thrashing
Always aim to prevent layout thrashing. Common strategies include batching reads and writes.
Reading position or style information on a fully cached, styled, and positioned render tree is fast.
Writing position information marks nodes as dirty but does not immediately force a reflow.
By separating reads from writes, the expensive reflow cost occurs only once during the asynchronous reflow phase.
const elements = [...document.querySelectorAll(".some-class")];
// Do all reads
const rects = elements.map(element => element.getBoundingClientRect());
// Do all writes
elements.forEach(element => element.classList.add("width-adjust"));
// Done! Asynchronous reflow will compute positions later.Browser APIs That Trigger Reflow
When developing web applications, be aware of APIs that can force reflow (e.g., offsetWidth, scrollTop, getComputedStyle). Use them judiciously and profile performance to avoid accidental synchronous reflows.
React Development
Using React does not eliminate layout thrashing. The declarative nature of React can hide DOM mutations that invalidate layout, especially when measuring DOM nodes inside useEffect.
function MyComponent() {
const elementRef = React.useRef();
// Be careful with Layout APIs in `useEffect`!
React.useEffect(() => {
const rect = elementRef.getBoundingClientRect();
// do something with `rect`
}, []);
return <div ref={elementRef}>/* more DOM nodes... */</div>;
}If you need to read layout after React has flushed updates, use useLayoutEffect instead.
function MyComponent() {
const elementRef = React.useRef();
// Use the `useLayoutEffect` hook instead if you are forcing reflow.
React.useLayoutEffect(() => {
const rect = elementRef.getBoundingClientRect();
// Use the values read from `rect`
// Writing to the DOM here will likely cause more reflow!
}, []);
return <div ref={elementRef}>/* more DOM nodes... */</div>;
}Conclusion
We covered the complex lifecycle of browser reflows, including asynchronous reflow, forced synchronous reflow, and layout thrashing. Web developers must understand when and how the browser’s layout engine runs to leverage its incremental design, avoid performance bottlenecks, and deliver smooth frame rates and optimal user experiences.
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.
