Layout thrashing occurs when JavaScript alternates between DOM reads (which force the browser to recalculate layout) and DOM writes within the same synchronous block. A loop that reads el.offsetHeight then writes el.style.height for 50 elements triggers 50 layout recalculations instead of one. ISO 25010:2011 time-behaviour measures the cost; the user-visible outcome is continuous paint flashing and dropped frames during any interaction that touches DOM geometry — typically resizing, drag, and scroll handlers.
Info because layout thrashing produces cosmetic jank and is difficult to trigger in synthetic tests, making it a polish-level issue rather than a blocking defect — though it can be severe on devices with constrained GPU memory.
Separate all DOM reads from DOM writes by batching writes into a requestAnimationFrame callback. Read geometry values before the frame, apply changes inside the callback.
// components/resize-handler.ts — avoid layout thrashing
function resizeElements(elements: HTMLElement[]) {
// Phase 1: read all geometry (single layout pass)
const heights = elements.map((el) => el.offsetHeight)
// Phase 2: write all geometry in next frame (single repaint)
requestAnimationFrame(() => {
elements.forEach((el, i) => {
el.style.height = heights[i] + 10 + 'px'
})
})
}
ID: performance-deep-dive.regression-prevention.paint-profiling-clean
Severity: info
What to look for: Count all DOM manipulation patterns. Enumerate locations where DOM reads and writes are interleaved (potential layout thrashing). Use Chrome DevTools → Rendering tab. Enable "Paint flashing" and interact with the app. Look for excessive red squares indicating repaints. Check for "layout thrashing" patterns (rapid reads and writes to DOM).
Pass criteria: Paint profiling shows no more than 5 repaint events per user interaction. No layout storms or thrashing detected. DOM reads and writes are batched. Zero layout thrashing patterns detected (interleaved read-write sequences in loops).
Fail criteria: Frequent repaints indicated by continuous red flashing. Layout thrashing detected (read, write, read, write pattern).
Skip (N/A) when: Never — paint profiling applies to all applications.
Cross-reference: For GC pause impact on paint, see gc-pauses-under-50ms.
Detail on fail: "Continuous paint flashing during scroll — indicates excessive repaints and poor performance" or "Layout thrashing detected: DOM reads and writes interleaved in tight loop"
Remediation: Batch DOM operations to avoid layout thrashing.
// Batch DOM operations in components/resize-handler.ts
const height = el.offsetHeight // read
requestAnimationFrame(() => { el.style.height = height + 10 + 'px' }) // write in next frame