31 · 7 stages
Visualize / 31

The browser rendering pipeline.

HTML arrives, then CSS, then JS. The browser turns all of that into a picture on your screen in six well-defined stages. Knowing which stage a given DOM change triggers (recompute style? layout? paint?) is the difference between 16 ms frames and 100 ms frames.


stage 1 / 6
STAGE 1HTML parsetokens → DOM tree15 ms STAGE 2CSS parserules → CSSOM8 ms STAGE 3Styleattach CSS to DOM4 ms STAGE 4Layoutcompute geometry12 ms STAGE 5Paintpixels per layer6 ms STAGE 6Compositemerge to one image3 ms
1. HTML parse · 15 ms

HTML bytes stream in. Parser tokenizes, builds the DOM tree. Encountering a <link rel="stylesheet"> blocks render but not parsing — CSS fetches in parallel. <script> blocks parsing unless marked async/defer.

Which DOM changes trigger which stages

Style only: changing color or visibility recalculates style and repaints, no layout. Layout: changing width, top, display, font-size — anything that affects geometry. Triggers all downstream stages. Composite only: transform and opacity on a promoted layer. Skips paint and layout entirely. Cheap enough to animate at 60 FPS even on weak GPUs. Animating top/left causes layout per frame; using transform: translate() instead is the single most common perf win.

Layout thrashing

Most "JS is slow" performance bugs are actually layout thrashing in disguise. The pattern: in a loop, write to the DOM (changing a width), then read a layout-triggering property (offsetWidth, getBoundingClientRect). The read forces a synchronous layout because the previous write invalidated the cached geometry. Do this 100 times in a loop and you get 100 layouts when you should have 1. Fix: batch all reads, then all writes. requestAnimationFrame + a virtual DOM diff makes this automatic.

The 16 ms budget

60 Hz displays redraw every 16.67 ms. To not drop frames, the browser has to finish style, layout, paint, composite within that budget — minus a few ms for the OS compositor and GPU. If your scroll handler does 30 ms of layout, you drop one frame. If it does 70 ms, you drop four and the page feels janky. Chrome\'s Performance tab is the tool: record, scroll, look for the long purple Layout bars. Each one is a fixable bug.

Go deeper

Frontend performance →

Critical rendering path, layer promotion, content-visibility, will-change, the new view-transitions API, why React batches renders.

Open the Codex →
Found this useful?