Frames, layout thrashing and compositing
Measure when geometry is ready; avoid repeatedly asking the browser to rebuild it inside one operation.
Predict first
Before you read: predict
A proctor tile starts at width 100px with clean layout. One callback executes the following.
How many synchronous Layout records occur inside it?
for (let i = 0; i < 4; i++) { tile.style.width = `${101 + i}px`; log(tile.offsetWidth);}The 30-second version
A frame may run JavaScript, calculate styles, lay out geometry, paint pixels, then composite layers. A geometry write invalidates layout; a subsequent geometry read can force it immediately. Alternating writes and reads repeats that work. Batch compatible operations. Transform and opacity can avoid layout and paint on compositor layers. A requestAnimationFrame callback runs before the frame's normal rendering work.
Measure when geometry is ready; avoid repeatedly asking the browser to rebuild it inside one operation.
How it works
Step 1 of 7
Analogy: Adjusting a surveyor's tripod
An operator moves one leg, checks a spirit level, moves another leg, and checks again. Each check after an adjustment concerns a new geometry. If the task permits it, the operator makes all planned adjustments and checks once. A second glance with no adjustment uses the same configuration.
| Tripod scene | Browser mechanism |
|---|---|
| Leg position changed | Geometry invalidated |
| Inspecting the new level | Reading current geometry |
| Inspection after each adjustment | Repeated synchronous layout |
| All compatible adjustments before inspection | Batched writes followed by measurement |
In real terms: schedule measurements around the geometry they actually need. Breaks down when: real surveying requires feedback after each move; some browser algorithms do too. Batching is valid only if the dependency permits it. Contrasting case: repainting a coloured mark does not move a tripod leg; similarly, not every visual change requires layout. S1
The task and microtask queues belong to the event-loop record. Interaction phases belong to INP. Neither scheduling vocabulary alone nor knowing that a click is slow identifies a layout dependency.
Examples, run for real
4.1 ExamHall: repeated measurements
EXH-1590, Proctor tile loop forces repeated synchronous layout. The live-feed summary box starts with clean geometry. Eight updates each change its width and immediately read offsetWidth. mechanisms.mjs brackets the callback with performance marks and counts Chromium Layout records between them. Every mutation changes the numeric width, so this is not a test of redundant assignments. S2
worked layout counts: interleaved=8 batched=1 reads-only=0
MR !1790, Batch tile geometry mutations before measurement, keeps the final geometry requirement but moves its read after the compatible updates. That produces one synchronous layout. The reads-only control measures a clean tree and produces none. This repair is suitable when intermediate measurements are unused; if each update depends on its own measured result, silently moving all reads would change the program. S2
4.2 Catalog: choosing the visual property
A promoted product badge is moved using transform; another run varies its opacity. Each run also reads offsetWidth, keeping the measurement operation visible. Both have zero synchronous layouts. Controls using width mutations do require layout. These observations establish the geometry distinction, not a universal promise that any transform animation is free. Initial layer creation, rasterisation and unrelated page changes remain possible costs. S3
worked visual property counts: transform=0 opacity=0 width=1
4.3 A callback is before the rendering work
A badge's requestAnimationFrame callback changes width without reading geometry. A following rendering opportunity completes the observation:
worked rAF trace after callback: UpdateLayoutTree -> Layout -> Paint
This distinguishes a callback scheduled before rendering from a callback after pixels are painted. It does not promise one paint for every callback or a fixed frame rate. The fixture is visible, changes geometry, and waits for the next animation callback to collect its trace. S4
Try it
Answer first, then check. Nothing is saved.
Question 1 of 5
A product panel receives five different width assignments, then one offsetWidth read. It begins clean.
How many synchronous layouts occur inside the callback?
Question 2 of 5
A relatively positioned badge must move through three requested horizontal positions. Its width measurement is needed during the callback.
Which implementation avoids synchronous layouts while moving it?
Question 3 of 5
A visible tile changes height inside requestAnimationFrame, without reading geometry there.
Which trace order follows the callback-end mark?
Question 4 of 5
A review groups six price-panel width updates into three pairs. After each pair it reads offsetWidth. The reviewer says pairing the writes has removed all repeated layout.
Which diagnosis fits the callback?
Question 5 of 5
A panel starts at 100px. Seven serial pricing rules each set its next width to Math.round(currentRenderedWidth * 1.1).
What final width does the browser model return?
Go deeper
Pro tip
Name performance marks around the exact operation under investigation before recording a trace. This makes it possible to distinguish work inside the callback from later rendering. Tool move, not an assessment repair; User Timing API, read 2026-09-29.
Sources
Fetched 2026-09-29; Chromium observations apply to 153.0.8010.12.
- S1: Paul Lewis, Rendering performance: stages and optional paths.
- S2: Jeremy Wagner, Paul Lewis and Barry Pollard, Avoid large, complex layouts and layout thrashing: invalidation, reads and batching.
- S3: Paul Lewis, Stick to compositor-only properties and manage layer count: transform, opacity and layer costs.
- S4: MDN, requestAnimationFrame: callback before repaint; browser scheduling limits.
Checked against the tools and docs as of 2026-09-29.