A reflow (layout) recomputes box sizes and positions; a repaint redraws pixels without moving anything (How CSS Becomes Pixels maps properties to stages). Writes such as el.style.width = '50px' do not reflow on the spot: the engine marks boxes dirty and does the work once, before the next frame.
Reads break that deal. Asking for offsetWidth, clientHeight, scrollTop, getBoundingClientRect() or a computed width while layout is dirty makes the browser run layout synchronously, inside your script, to return a correct number. One forced layout per loop iteration is layout thrashing, measured below in headless Chrome 152 1 .
const bars = Array.from({ length: 2000 }, () => {
const bar = document.body.appendChild(document.createElement('div'));
bar.style.cssText = 'height: 2px; width: 100px; background: teal';
return bar;
});
function thrash() { // read, write, read, write: every read forces a layout
for (const bar of bars) bar.style.width = `${bar.offsetWidth + 1}px`;
}
function batched() { // all reads first, then all writes
const widths = bars.map((bar) => bar.offsetWidth);
bars.forEach((bar, i) => { bar.style.width = `${widths[i] + 1}px`; });
}
for (const fn of [thrash, batched]) {
const t0 = performance.now();
fn();
bars[0].offsetWidth; // include the final layout in the timing
console.log(`${fn.name}: ${(performance.now() - t0).toFixed(1)} ms`);
}thrash: 1140.5 ms batched: 6.1 ms
The same writes cost almost 190 times more when interleaved with reads. Swapping offsetWidth for getComputedStyle(bar).color in the thrashing loop took 8.7 ms in the same browser: color needs style, not layout.
To keep DOM code fast:
Group reads before writes; defer writes from separate components with requestAnimationFrame() (requestAnimationFrame). FastDOM 6,914 (https://github.com/wilsonpage/fastdom 6,914 ) (npm 2,036 install fastdom, MIT) automates this: fastdom.measure() and fastdom.mutate() queue callbacks and run reads, then writes, in the next frame.
Toggle one class instead of setting many inline styles, and build new subtrees in a DocumentFragment before inserting them once (Delegation and Batching).
Find offenders with the Forced reflow insight in the Chrome DevTools 3,948 Performance panel.