Rendering Pipeline
From HTML to pixels. How the browser draws your page at 60fps.
Overview
The 5-step pipeline from code to pixels.
Critical Rendering Path:
1Parse HTML → DOM Tree
2Parse CSS → CSSOM Tree
3Combine → Render Tree
4Layout → Calculate positions & sizes
5Paint & Composite → Pixels on screen
// Every time you see a web page, the browser: // 1. Downloads HTML and builds the DOM // 2. Downloads CSS and builds the CSSOM // 3. Merges them into a Render Tree (only visible elements) // 4. Calculates where everything goes (Layout/Reflow) // 5. Paints pixels and composites layers // This happens at ~60fps (every 16.6ms) for smooth animation. // JavaScript can trigger parts of this pipeline to re-run. // Goal: keep the pipeline fast (< 16ms per frame) // to avoid "jank" (visible stuttering)
DOM & CSSOM
Parsing HTML and CSS into tree structures.
// HTML → DOM Tree (Document Object Model):
// <html>
// <body>
// <h1>Hello</h1>
// <p class="text">World</p>
// </body>
// </html>
//
// Becomes:
// Document
// └─ html
// └─ body
// ├─ h1 → "Hello"
// └─ p.text → "World"
// CSS → CSSOM Tree:
// body { font-size: 16px; }
// .text { color: blue; }
//
// Becomes a tree of computed styles:
// body → { font-size: 16px, display: block, ... }
// └─ p.text → { color: blue, font-size: 16px (inherited), ... }
// ⚠️ CSS is render-blocking:
// Browser won't render until CSSOM is complete.
// That's why <link rel="stylesheet"> goes in <head>.
// ⚠️ JS is parser-blocking:
// <script> tags pause HTML parsing until script runs.
// Use "defer" or "async" to avoid this:
// <script src="app.js" defer></script>Layout
Calculate the exact size and position of every element.
// The Render Tree = DOM + CSSOM (visible elements only) // display: none → excluded from render tree // visibility: hidden → included (takes space) // Layout (aka Reflow) calculates: // - Width and height of each box // - Position (x, y) on the page // - How elements flow and wrap // Layout is EXPENSIVE — avoid triggering it unnecessarily! // These properties trigger layout: element.style.width = "200px"; // layout element.style.height = "100px"; // layout element.style.margin = "10px"; // layout element.style.padding = "5px"; // layout element.style.top = "50px"; // layout (if positioned)
Layout is the most expensive step. Changing size/position properties forces the browser to recalculate geometry for affected elements.
1 / 2
Paint & Composite
Turn geometry into actual pixels.
// Paint: draw pixels for each layer // - Text, colors, borders, shadows, images // - Broken into "paint records" (draw instructions) // Composite: combine layers in the correct order // - Each layer is painted independently (on GPU) // - Then layers are stacked together // - transform and opacity changes skip Layout AND Paint! // The rendering cost hierarchy: // MOST EXPENSIVE: element.style.width = "200px"; // Layout → Paint → Composite element.style.fontSize = "18px"; // Layout → Paint → Composite // MEDIUM: element.style.color = "red"; // Paint → Composite (no layout) element.style.background = "blue"; // Paint → Composite // CHEAPEST (compositor-only): element.style.transform = "translateX(10px)"; // Composite only! element.style.opacity = "0.5"; // Composite only! // That's why CSS animations should use transform/opacity // — they're the only properties that skip Layout and Paint
Layout
Most expensive
Paint
Medium
Composite
Cheapest
JS & Rendering
How JavaScript interacts with the rendering pipeline.
// JS runs BEFORE rendering in each frame:
// [JS] → [Style] → [Layout] → [Paint] → [Composite]
// requestAnimationFrame — sync JS with rendering:
function animate() {
// Runs right before the browser renders
element.style.transform = `translateX(${x}px)`;
requestAnimationFrame(animate);
}
requestAnimationFrame(animate);
// ❌ Don't animate with setTimeout:
setInterval(() => {
element.style.left = x++ + "px"; // triggers layout!
// Also: not synced with display refresh → janky
}, 16);
// ✓ Best practices for smooth 60fps:
// 1. Animate only transform and opacity
// 2. Use requestAnimationFrame for JS animations
// 3. Use CSS transitions/animations when possible
// 4. Batch DOM reads and writes separately
// 5. Use will-change to promote elements to own layer:
// .animated { will-change: transform; }
// Measure rendering performance:
performance.mark("start");
// ... do work ...
performance.mark("end");
performance.measure("work", "start", "end");FAQ
Common questions about browser rendering.