Loading...
Loading...
From HTML to pixels. How the browser draws your page at 60fps.
Module 10 · Browser Internals & Best Practices
Production JavaScript requires more than syntax. The event loop, rendering pipeline, bundlers, debugging techniques, performance optimization, design patterns, testing, and security practices separate hobby scripts from professional software.
Learn Rendering Pipeline in JavaScript — covering Overview, DOM & CSSOM, Layout, Paint & Composite with interactive visual examples and step-by-step explanations.
Writing JavaScript that works in a tutorial is different from writing JavaScript that ships to production. The event loop explains why setTimeout(0) does not run immediately. Performance profiling finds bottlenecks before users do.
This module covers the internals and best practices that separate hobby scripts from professional software: debugging, bundlers, design patterns, testing strategies, and security fundamentals every developer should know.
Understanding rendering pipeline is essential for every JavaScript developer. It shows up in frontend UI code, backend APIs, and framework internals — and getting it wrong leads to bugs that are hard to trace. This chapter builds intuition with visuals so the behavior sticks.
The 5-step pipeline from code to pixels.
Critical Rendering Path:
// 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)
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>
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.
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
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");
Common questions about rendering pipeline.