Layout thrashing (forced reflow)

Reading a layout value right after changing one, in a loop, forcing the browser to recompute the page's geometry every single time.

the page recalculates over and overmeasuring and changing in a loopforced reflowforced synchronous layoutlayout trashingreading offsetHeight makes it jankymy scroll handler stuttersdevtools says forced reflow took 40ms

See it

Live demo coming soon

What it is

Browsers are lazy on purpose: they queue up your DOM writes and only recompute geometry once, at the end of the frame. The moment your code asks for a measured value (offsetTop, offsetWidth, getBoundingClientRect, scrollTop, getComputedStyle) the browser has to stop and flush layout immediately so it can answer honestly. That is a forced synchronous layout. Do it once and nobody notices. Do it inside a loop that also writes styles, and you have recomputed the whole page layout two hundred times in one frame.

The fix is separation, not cleverness: read every measurement you need into an array first, then do all the writes. Schedule the writes in a requestAnimationFrame callback (the FastDom pattern). Better still, stop measuring: ResizeObserver and IntersectionObserver hand you geometry without forcing a flush, and animating with transform and opacity skips layout entirely.

Gotchas: the trigger list is longer than anyone remembers, and includes scrollTop, focus(), innerText, and the CSS Typed OM's computed values. The loop is rarely one obvious for-loop either, it hides as a helper that measures then styles, called from inside a map. Chrome DevTools flags it in the performance panel as 'Forced reflow while executing JavaScript took Xms', with a purple layout bar right after your script.

Ask AI for it

Audit this code for layout thrashing and fix it. Find every place a DOM write is followed by a geometry read (offsetTop, offsetWidth, offsetHeight, getBoundingClientRect, scrollTop, getComputedStyle) inside a loop or an event handler. Refactor each into two passes: collect all measurements into an array first, then apply all mutations in a single requestAnimationFrame callback. Replace polling-style measurement with ResizeObserver or IntersectionObserver where it fits, and convert any animated top/left/width/height to transform. Add a short comment at each site naming the read/write split.

You might have meant

main thread blockinglong tasktotal blocking timeinteraction to next paintcumulative layout shift