How Browsers Actually Work
From HTTP Request to Painting Pixels β Understanding the Critical Rendering Path and Optimization Techniques
The Journey Overview
When you type a URL and press Enter, a complex sequence of events unfolds. Understanding this journey is crucial for optimizing web performance.
1. URL ENTERED β 2. DNS LOOKUP (Domain β IP Address) β 3. TCP CONNECTION (3-way handshake) β 4. TLS HANDSHAKE (if HTTPS) β 5. HTTP REQUEST SENT β 6. SERVER PROCESSES & RESPONDS β 7. BROWSER RECEIVES HTML β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β CRITICAL RENDERING PATH BEGINS β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€ β 8. HTML PARSING β DOM Construction β β β β β 9. CSS PARSING β CSSOM Construction β β β β β 10. JavaScript Execution (can block!) β β β β β 11. RENDER TREE = DOM + CSSOM β β β β β 12. LAYOUT (Reflow) - Calculate positions & sizes β β β β β 13. PAINT - Fill in pixels β β β β β 14. COMPOSITE - Layer management β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β 15. π PAGE IS VISIBLE TO USER!
DNS Resolution
When you type https://example.com, the browser needs to find the IP address of that server. This process can take 20-120ms!
Browser: "What's the IP for example.com?" 1. Browser Cache β Check if we visited recently β (miss) 2. OS Cache β Check operating system's cache β (miss) 3. Router Cache β Check your router β (miss) 4. ISP DNS Server β Your Internet Provider's DNS β (miss) 5. Recursive Search: βββββββββββββββββββββββββββββββββββββββββββββββββββ β Root DNS (.com, .org, .net servers) β β β β β TLD Server (Top Level Domain - .com) β β β β β Authoritative DNS (example.com's own server) β β β β β IP Address: 93.184.216.34 β βββββββββββββββββββββββββββββββββββββββββββββββββββ β±οΈ This process can take 20-120ms!
DNS Optimization Techniques
| Technique | How it Works |
|---|---|
| DNS Prefetch | <link rel="dns-prefetch" href="//api.example.com"> |
| Preconnect | <link rel="preconnect" href="https://fonts.googleapis.com"> |
| Reduce DNS lookups | Use fewer external domains |
| DNS Caching | Browser/OS cache frequently visited domains |
TCP Handshake
TCP (Transmission Control Protocol) ensures reliable, ordered delivery of data. Before sending anything, both parties must agree to communicate through a 3-way handshake.
CLIENT (Browser) SERVER
β β
β β
Step 1: β βββββββββββ SYN βββββββββββββββββββΆ β
"Hey, β seq=100 (random number) β
wanna β "I want to connect!" β
talk?" β β
β β
β β Step 2:
β βββββββββββ SYN-ACK βββββββββββββββ β "Sure! I hear you,
β seq=300, ack=101 β and I want to
β "Got it! I also want to connect!" β connect too!"
β β
β β
Step 3: β βββββββββββ ACK βββββββββββββββββββΆ β
"Great, β seq=101, ack=301 β
let's β "Confirmed! Let's go!" β
go!" β β
β β
β βββββββββββββββββββββββββββββββββββ β
β CONNECTION ESTABLISHED! β
β±οΈ Time: ~1 RTT (Round Trip Time)
π Typical: 20-100ms depending on distance
Step 1 (SYN): Client proves it can SEND
Step 2 (SYN-ACK): Server proves it can SEND and RECEIVE
Step 3 (ACK): Client proves it can RECEIVE
TLS Handshake
After TCP connection is established, if using HTTPS, we need another handshake to establish encryption.
TLS 1.2 vs TLS 1.3
2 RTT for handshake
Multiple round trips for key exchange
Time: ~60-100ms
1 RTT for handshake
Key share sent in ClientHello
Time: ~30-50ms
0-RTT for session resumption!
TIME CLIENT SERVER
β ββββββββ DNS Query βββββββββββββββββββββΆ
β ββββββββ DNS Response βββββββββββββββββ
0ms DNS Resolution: ~50ms
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β ββββββββ SYN βββββββββββββββββββββββββββΆ
β ββββββββ SYN-ACK ββββββββββββββββββββββ
β ββββββββ ACK βββββββββββββββββββββββββββΆ
50ms TCP Handshake: ~30ms (1 RTT)
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β ββββββββ ClientHello βββββββββββββββββββΆ
β ββββββββ ServerHello, Certificate βββββ
β ββββββββ Key Exchange, Finished ββββββββΆ
β ββββββββ Finished βββββββββββββββββββββ
140ms TLS Handshake: ~60ms (2 RTT for TLS 1.2)
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β ββββββββββ SECURE CONNECTION ββββββββββ
β ββββββββ HTTP Request ββββββββββββββββββΆ
β ββββββββ HTTP Response ββββββββββββββββ
β±οΈ Total before first byte: ~200ms minimum!
With TLS 1.3: ~140ms (saves one RTT)
With 0-RTT: ~80ms (connection reuse)
HTML Parsing & DOM Construction
The browser transforms raw bytes into a structured DOM tree through a multi-step pipeline.
Parser Blocking
<html>
<body>
<h1>Title</h1> β
Parsed
<script src="app.js"> π STOP! Must fetch & execute
</script>
<p>Content below</p> βΈοΈ WAITING... (not parsed yet)
</body>
</html>
WHY does script block?
Script might call document.write() which can CHANGE the HTML!
Parser can't continue until it knows what the script will do.
Preload Scanner
While the main parser is blocked, a secondary scanner keeps looking ahead for resources to fetch in parallel (images, CSS, scripts). This can significantly speed up page load!
CSSOM Construction
Unlike DOM which mirrors HTML structure, CSSOM mirrors CSS structure.
document.styleSheets
β
β StyleSheetList (array-like)
β
βββ [0] CSSStyleSheet (user-agent styles)
β β
β βββ .cssRules: CSSRuleList
β
βββ [1] CSSStyleSheet (your styles.css)
β β
β βββ .cssRules: CSSRuleList
β β βββ [0] CSSStyleRule
β β β βββ .selectorText: "body"
β β β βββ .style: { margin: "0" }
β β β
β β βββ [1] CSSMediaRule (@media query)
β β β βββ .cssRules: (nested rules)
β β β
β β βββ [2] CSSKeyframesRule
β β
β βββ .href: "https://example.com/styles.css"
β
βββ [2] CSSStyleSheet (inline <style>)
CSS is RENDER-BLOCKING
DOM ready β + CSSOM loading β³ = NO PAINT π«
DOM ready β + CSSOM ready β = CAN PAINT β
Why? To prevent FOUC (Flash Of Unstyled Content). The browser will show a blank screen rather than unstyled content.
Multiple CSS Files
If you have 3 CSS files and one loads in 100ms but others take 4 seconds, CSSOM waits for ALL render-blocking CSS files. First paint happens after the SLOWEST file loads.
Async CSS Loading
<!-- Media trick - load CSS without blocking -->
<link rel="stylesheet" href="styles.css"
media="print" onload="this.media='all'">
<!-- Preload + onload technique -->
<link rel="preload" href="styles.css" as="style"
onload="this.rel='stylesheet'">
<!-- Fallback for browsers without JS -->
<noscript><link rel="stylesheet" href="styles.css"></noscript>
Render Tree
The Render Tree = DOM nodes that are VISIBLE + their COMPUTED STYLES. This is where DOM and CSSOM come together!
STEP 1: Start with DOM tree βββββββββββββββββββββββββββββ DOM CSSOM html body { margin: 0; } β h1 { color: blue; } ββββ΄βββ .hidden { display: none; } head body p::before { content: "β"; } β ββββββΌβββββ h1 .hidden p STEP 2: Walk DOM, attach computed styles βββββββββββββββββββββββββββββββββββββββββ For EACH DOM node: 1. Is it visible? (display: none = skip!) 2. Compute all styles (cascade + inheritance) 3. Create Render Object (attach styles) STEP 3: Result = Render Tree βββββββββββββββββββββββββββββ RenderView (viewport) β RenderBlock (body) styles: { margin: 0 } β βββββ΄βββββ β β RenderBlock RenderBlock (h1) (p) β β β βββββ΄βββββ β β β RenderText RenderInline RenderText "Title" (::before) "Content" content: "β" β .hidden element NOT in Render Tree! (display: none removes it completely)
DOM TREE (Complete structure): RENDER TREE (Visual only): Document RenderView β β <html> RenderBlock β (viewport) βββββββ΄ββββββ β β β RenderBlock <head> <body> (body) β β β ββββββ΄βββββ ... ββββββββββΌβββββββββ β β β β β β <meta><title><style> RenderBlock RenderBlock RenderBlock β β β (header) (main) (p) β β β (Not in render tree - RenderText β RenderText non-visual elements) ("Header") β β ββββββββ΄βββββββ β β RenderBlock RenderBlock (p) (hidden-p) β β ββββββ΄βββββ (display:none β β NOT in tree!) RenderInline RenderText (::before) ("Visible")
Style Computation (The Cascade)
For element: <p class="intro" id="welcome">Hello</p> STEP 1: Find all matching rules ββββββββββββββββββββββββββββββββββ Matches: p { color: black; font-size: 16px; } /* (0,0,0,1) */ .intro { color: blue; line-height: 1.5; } /* (0,0,1,0) */ #welcome { color: green; } /* (0,1,0,0) */ p.intro { font-weight: bold; } /* (0,0,1,1) */ STEP 2: Apply cascade (specificity order) ββββββββββββββββββββββββββββββββββββββββββββ Specificity = (inline, id, class, element) color: black (0,0,0,1) β overwritten color: blue (0,0,1,0) β overwritten color: green (0,1,0,0) β WINS! (highest specificity) STEP 3: Inherit from parent (for inheritable props) βββββββββββββββββββββββββββββββββββββββββββββββββββββ Parent (body): font-family: Arial; Element (p): inherits font-family: Arial STEP 4: Apply defaults for missing properties βββββββββββββββββββββββββββββββββββββββββββββββββ display: not specified β default: inline (for p: block) margin: not specified β default: browser stylesheet FINAL COMPUTED STYLE: βββββββββββββββββββββ { color: green, // from #welcome (highest specificity) font-size: 16px, // from p line-height: 1.5, // from .intro font-weight: bold, // from p.intro font-family: Arial, // inherited from body display: block, // browser default for p margin: 1em 0, // browser default for p ... // all 300+ CSS properties! }
Specificity Calculator
SPECIFICITY FORMAT: (inline, ID, class, element) βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β β β style="" β (1,0,0,0) β Inline ALWAYS wins! β β #id β (0,1,0,0) β β .class β (0,0,1,0) β β [attr] β (0,0,1,0) β Same as class β β :pseudo-class β (0,0,1,0) β Same as class β β element β (0,0,0,1) β β ::pseudo-elem β (0,0,0,1) β Same as element β β * β (0,0,0,0) β Universal = no specificity β β β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ EXAMPLES: p β (0,0,0,1) p.intro β (0,0,1,1) div p.intro β (0,0,1,2) #header .nav a β (0,1,1,1) #header .nav a:hover β (0,1,2,1) div#main p.text span β (0,1,1,3) COMPARISON (left to right): (0,1,0,0) beats (0,0,99,99) β IDs beat any number of classes!
What's NOT in the Render Tree?
<head>,<meta>,<title>,<script>,<style>β non-visual elements- Elements with
display: noneβ completely removed <template>elements β content not rendered- Hidden
<input type="hidden">
What IS in the Render Tree (even if not visible)?
- Elements with
visibility: hiddenβ takes space! - Elements with
opacity: 0β takes space, can be animated! - CSS-generated content (
::before,::after)
display:none vs visibility:hidden vs opacity:0
| Property | In Render Tree | Takes Space | Clickable | Animatable |
|---|---|---|---|---|
display: none |
β No | β No | β No | β No |
visibility: hidden |
β Yes | β Yes | β No | β οΈ Jumps |
opacity: 0 |
β Yes | β Yes | β Yes | β Smooth |
Layout (Reflow)
Layout calculates exact position (x, y) and size (width, height) for every element, transforming relative values to absolute pixels.
Render Tree knows: Layout calculates: RenderBlock (div) RenderBlock (div) styles: { layout: { width: 50%, β 50% of WHAT? x: 200, β Exact X position padding: 2em, β how many pixels? y: 100, β Exact Y position margin: auto β what value? width: 600, β 50% of 1200px = 600px } height: 400, β Calculated from content padding: 32, β 2em Γ 16px = 32px margin-left: 200 β auto calculated } Layout transforms RELATIVE values β ABSOLUTE pixel values β’ % β pixels (based on parent) β’ em β pixels (based on font-size) β’ auto β pixels (calculated from context) β’ vw/vh β pixels (based on viewport) β’ calc()β pixels (computed result)
βββββββββββββββββββββββββββββββββββββββββββ β MARGIN β β βββββββββββββββββββββββββββββββββββββ β β β BORDER β β β β βββββββββββββββββββββββββββββββ β β β β β PADDING β β β β β β βββββββββββββββββββββββββ β β β β β β β β β β β β β β β CONTENT BOX β β β β β β β β (width Γ height) β β β β β β β β β β β β β β β βββββββββββββββββββββββββ β β β β β βββββββββββββββββββββββββββββββ β β β βββββββββββββββββββββββββββββββββββββ β βββββββββββββββββββββββββββββββββββββββββββ box-sizing: content-box (default) β width/height applies to content only β Total = width + padding + border β Example: width: 200px; padding: 20px; border: 5px; Total = 200 + 20 + 20 + 5 + 5 = 250px box-sizing: border-box (recommended!) β width/height includes padding and border β Total = exactly what you specify β Example: width: 200px; padding: 20px; border: 5px; Content = 200 - 20 - 20 - 5 - 5 = 150px Total = 200px (as specified!)
Layout Algorithm
STEP 1: Start at root (viewport) βββββββββββββββββββββββββββββββββ Viewport size is known (e.g., 1920Γ1080) This is the "containing block" for the document viewport = { width: 1920, height: 1080, x: 0, y: 0 } STEP 2: Calculate widths (TOP-DOWN) ββββββββββββββββββββββββββββββββββββββ Width often depends on parent, so go parent β child Parent: width = 1000px Child: width = 50% β Child computed width = 500px STEP 3: Calculate heights (BOTTOM-UP) βββββββββββββββββββββββββββββββββββββββ Height often depends on children, so go child β parent Child 1: height = 100px Child 2: height = 150px Child 3: height = 80px β Parent height = 100 + 150 + 80 = 330px STEP 4: Calculate positions (x, y coordinates) ββββββββββββββββββββββββββββββββββββββββββββββββββ Based on: β’ Display mode (block, inline, flex, grid) β’ Position property (static, relative, absolute, fixed) β’ Float property β’ Margins (including margin collapse!)
Layout Modes (Formatting Contexts)
BLOCK FORMATTING CONTEXT (BFC) ββββββββββββββββββββββββββββββ Elements with display: block, flex, grid, table βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β Block 1 (full width) β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β Block 2 (full width) β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ Rules: β’ Each block on its own line (vertical stacking) β’ Width fills container (unless specified) β’ Vertical margins COLLAPSE between siblings ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ INLINE FORMATTING CONTEXT (IFC) βββββββββββββββββββββββββββββββ Elements with display: inline, inline-block ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β This is [inline1] text with [inline2] elements that [in- β β line3] wrap to the next line when they run out of space. β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ Rules: β’ Elements flow horizontally (like text) β’ Wrap to next line when no space β’ Vertical margins DON'T apply (use line-height) ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ FLEXBOX LAYOUT ββββββββββββββ display: flex ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β ββββββββββββ ββββββββββββ ββββββββββββ ββββββββββββ β β β Flex 1 β β Flex 2 β β Flex 3 β β Flex 4 β β β β flex:1 β β flex:2 β β flex:1 β β flex:1 β β β ββββββββββββ ββββββββββββ ββββββββββββ ββββββββββββ β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ GRID LAYOUT βββββββββββ display: grid; grid-template-columns: 1fr 2fr 1fr; ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β ββββββββββ ββββββββββββββββββββββββ ββββββββββ β β β 1 β β 2 β β 3 β β β ββββββββββ ββββββββββββββββββββββββ ββββββββββ β β ββββββββββ ββββββββββββββββββββββββ ββββββββββ β β β 4 β β 5 β β 6 β β β ββββββββββ ββββββββββββββββββββββββ ββββββββββ β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Margin Collapse
In Block Formatting Context, vertical margins can collapse! If Box 1 has margin-bottom: 30px and Box 2 has margin-top: 20px, the gap is only 30px (larger one wins), not 50px!
What Triggers Layout?
CSS PROPERTY CHANGES (that affect geometry): β’ width, height, min-*, max-* β’ margin, padding, border-width β’ top, left, right, bottom (positioned elements) β’ font-size, font-family, font-weight β’ display, position, float β’ flex-*, grid-* DOM CHANGES: β’ Adding/removing elements β’ Changing text content β’ Moving elements READING LAYOUT PROPERTIES (Forces sync layout!): β’ offsetTop, offsetLeft, offsetWidth, offsetHeight β’ clientTop, clientLeft, clientWidth, clientHeight β’ scrollTop, scrollLeft, scrollWidth, scrollHeight β’ getComputedStyle() (some properties) β’ getBoundingClientRect() β οΈ Reading these FORCES browser to calculate layout immediately!
Layout Thrashing
β BAD: Causes layout thrashing! boxes.forEach(box => { const width = box.offsetWidth; // READ β Forces layout! box.style.width = (width + 10) + 'px'; // WRITE β Invalidates // Next iteration: READ again β Forces NEW layout! }); Timeline: [Layout][Layout][Layout][Layout][Layout] ... 100 times! If 100 boxes: 100 layout calculations = ~1 SECOND of blocking! βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β GOOD: Batch reads, then batch writes // BATCH READ (one layout calculation) const widths = Array.from(boxes).map(box => box.offsetWidth); // BATCH WRITE (no layout during writes) boxes.forEach((box, i) => { box.style.width = (widths[i] + 10) + 'px'; }); Timeline: [Layout]...[Paint] Total: ~10ms instead of 1 second! βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ VISUALIZATION: β Layout Thrashing: R W L R W L R W L R W L R W L R W L R W L R W L R W L R W L βββ΄ββ΄ββ΄ββ΄ββ΄ββ΄ββ΄ββ΄ββ΄ββ΄ββ΄ββ΄ββ΄ββ΄ββ΄ββ΄ββ΄ββ΄ββ΄ββ΄ββ΄ββ΄ββ΄ββ΄ββ΄ββ΄ββ΄ββ΄ββ΄ββ Many layout calculations interleaved! β Batched: R R R R R R R R R R β L β W W W W W W W W W W ββββββ Reads ββββββββ βββββββ Writes βββββββ Single layout!
boxes.forEach(box => {
const width = box.offsetWidth; // READ
box.style.width = width + 'px'; // WRITE
// Repeat = Layout every iteration!
});
// Batch reads
const widths = boxes.map(b => b.offsetWidth);
// Batch writes
boxes.forEach((box, i) => {
box.style.width = widths[i] + 'px';
});
Paint
Paint converts Layout boxes into actual pixels using drawing commands (Paint Records). This is where the browser draws colors, images, text, shadows, and everything visual!
Layout tells us: Paint tells us: "This box is at "Fill this area with position (100, 50) blue (#0066ff), draw with size 200Γ100" a 2px black border, render text 'Hello' in Arial 16px" βββββββββββββββββββ βββββββββββββββββββ β β βββββββββββββββββββ β x: 100 β β ββ ββ β y: 50 β ββ Hello ββ β width: 200 β ββ ββ β height: 100 β βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββ Just geometry Actual pixels!
CSS:
.button {
background: linear-gradient(#4CAF50, #388E3C);
border: 2px solid #2E7D32;
border-radius: 8px;
box-shadow: 0 4px 6px rgba(0,0,0,0.3);
color: white;
font: bold 16px Arial;
}
Paint Records Generated:
1. DrawShadow(
x: 100, y: 54, // Offset for shadow
blur: 6,
color: rgba(0,0,0,0.3)
)
2. DrawRoundedRect(
x: 100, y: 50,
width: 200, height: 50,
radius: 8,
fill: LinearGradient(#4CAF50 β #388E3C)
)
3. StrokeRoundedRect(
x: 100, y: 50,
width: 200, height: 50,
radius: 8,
strokeWidth: 2,
strokeColor: #2E7D32
)
4. DrawText(
text: "Click Me",
x: 150, y: 80, // Centered position
font: "bold 16px Arial",
color: #FFFFFF
)
These commands are executed in ORDER to produce the final pixels!
Paint Order (Stacking Context)
Elements are painted in a SPECIFIC ORDER (back to front): PAINT ORDER (within each stacking context): 1. Background color of element 2. Background image 3. Border 4. Children (in DOM order) 5. Outline STACKING ORDER (z-axis, back to front): BACK (painted first) β 1. Root element background 2. Positioned elements with negative z-index 3. Non-positioned block elements (in DOM order) 4. Non-positioned float elements 5. Non-positioned inline elements (text, images) 6. Positioned elements with z-index: 0 or auto 7. Positioned elements with positive z-index β FRONT (painted last, on top)
What Triggers Paint?
| Property Type | Layout | Paint | Composite |
|---|---|---|---|
| width, height, margin, padding | β | β | β |
| color, background, box-shadow | β | β | β |
| transform, opacity | β | β | β |
Paint Cost
π’ CHEAP (fast to paint): ββββββββββββββββββββββββββ β’ Solid color backgrounds β’ Simple borders β’ Simple text .simple { background: #fff; border: 1px solid #ccc; color: #333; } βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ π‘ MEDIUM (more work): ββββββββββββββββββββββ β’ Gradients β’ Border-radius β’ Images .medium { background: linear-gradient(to bottom, #fff, #eee); border-radius: 10px; } βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ π΄ EXPENSIVE (slow to paint): βββββββββββββββββββββββββββββ β’ Box shadows (especially large blur radius) β’ Text shadows β’ Filters (blur, drop-shadow) β’ Clip-path (complex shapes) β’ Mix-blend-mode .expensive { box-shadow: 0 10px 50px rgba(0,0,0,0.5); // Large blur! filter: blur(5px) drop-shadow(10px 10px 10px #000); clip-path: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%); } βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ PAINT AREA MATTERS TOO: Small area with expensive effect: ββββββ βblurβ ~1ms (small area, still okay) ββββββ Large area with expensive effect: ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β BLUR ENTIRE PAGE β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ ~50ms+ (huge area = SLOW!) Paint cost = Complexity Γ Area
Paint Layers
WITHOUT LAYERS (Single Surface): βββββββββββββββββββββββββββββββββ Any change = Repaint EVERYTHING on that surface βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β ββββββββ Change here ββββββββββββββββΆ Repaint ALL! ββββββββββ β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ WITH LAYERS (Multiple Surfaces): ββββββββββββββββββββββββββββββββ Each layer is painted independently! Layer 3 (animated element): βββββββββββββββββββ β Button (hover) β βββ Only this layer repaints! βββββββββββββββββββ Layer 2 (fixed header): βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β Header β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ Layer 1 (main content): βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ Change in Layer 3 = Only repaint Layer 3! (much faster)
Paint Flashing: DevTools β Cmd+Shift+P β "Show paint flashing" β Green flash shows repainted areas
Layer Borders: Rendering tab β Check "Layer borders" β See compositor layers
Performance Panel: Record and see paint timing in the flame chart
Compositing
Compositing is the final step β GPU combines all painted layers into the final image. This runs on a separate compositor thread!
After Paint creates bitmaps (textures) for each layer: Layer Textures (in GPU memory): ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ β Layer 1 β β Layer 2 β β Layer 3 β β (content) β β (header) β β (modal) β β β β β β β β ββββββββββ β β ββββββββββ β β ββββββββββ β β β Bitmap β β β β Bitmap β β β β Bitmap β β β ββββββββββ β β ββββββββββ β β ββββββββββ β ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ β β β ββββββββββββββββββββΌββββββββββββββββββ β βΌ βββββββββββββββββββ β COMPOSITOR β β β β β’ Position β β β’ Transform β β β’ Opacity β β β’ Blend β ββββββββββ¬βββββββββ β βΌ βββββββββββββββββββ β FINAL FRAME β β (to screen) β βββββββββββββββββββ Key insight: GPU can transform/blend textures WITHOUT repainting!
MAIN THREAD COMPOSITOR THREAD β’ JavaScript β’ Layer composition β’ DOM manipulation β’ Transform animations β’ Style calculation β’ Opacity animations β’ Layout β’ Scrolling β’ Paint (generate records) β’ Pinch-zoom Can be BLOCKED by NEVER blocked by heavy JavaScript! JavaScript! βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ Scenario: Heavy JavaScript running (500ms) Main Thread: βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β ββββββββββββββββββββββββ JavaScript (500ms) βββββββββββββββββ β β BLOCKED - Can't do layout/paint! β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ Compositor Thread: βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β βββ Composite βββ Composite βββ Composite βββ Composite βββ β β Still running! Smooth scrolling! 60fps animations! β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ Transform/opacity animations stay SMOOTH even when JavaScript is blocking the main thread!
Compositor-Only Operations (GPU Accelerated)
These operations can be performed by the GPU without repainting:
1. TRANSLATE (Move position) βββββββββββββββββββββββββββββ transform: translateX(100px) translateY(50px); βββββββββββ βββββββββββ β Layer β ββββββββββββΆ β Layer β βββββββββββ βββββββββββ Original Moved (GPU just repositions!) 2. SCALE (Resize) ββββββββββββββββββ transform: scale(1.5); βββββββββββ βββββββββββββββββ β Layer β ββββββββββββΆ β Layer β βββββββββββ βββββββββββββββββ Original Scaled (GPU stretches texture!) 3. ROTATE βββββββββ transform: rotate(45deg); βββββββββββ βββββββββββ β Layer β ββββββββββββΆ ββ Layer ββ βββββββββββ βββββββββββ Original Rotated (GPU rotates texture!) 4. OPACITY (Transparency) βββββββββββββββββββββββββββ opacity: 0.5; βββββββββββ β β β β β ββ βββββββββββ ββββββββββββΆ ββββββββββββ βββββββββββ β β β β β ββ Solid Semi-transparent (GPU blends with background!) ALL OF THESE: β’ Don't require repaint! β’ Run on compositor thread (off main thread!) β’ Are GPU accelerated (super fast!) β’ Can hit 60fps even with heavy JS!
What Creates Layers?
AUTOMATIC LAYER PROMOTION: β 3D transforms transform: translateZ(0) transform: translate3d(x, y, z) transform: rotateX/Y/Z() β CSS animation/transition on transform or opacity .box { transition: transform 0.3s; } @keyframes slide { to { transform: translateX(100px); } } β position: fixed Fixed elements often get their own layer β will-change property will-change: transform, opacity; β <video> and <canvas> elements Media always gets own layer β CSS filter or backdrop-filter filter: blur(10px); β Element overlapping a composited layer If A is layered and B overlaps A, B might get a layer too! β opacity < 1 opacity: 0.99 triggers layer creation!
Layer Tree Visualization
Example page with multiple layers:
βββββββββββββ
β±β Video β Layer 5 (front)
β± βββββββββββββ
β±
β± βββββββββββββ
β± β±β Modal β Layer 4
β± β± βββββββββββββ
β± β±
β± β± βββββββββββββ
β± β± β±β Card β Layer 3
β± β± β± βββββββββββββ
β± β± β±
β± β± βββββββββββββ
β± β± β±β Header β Layer 2
β± β± βββββββββββββ
β±
βββββββββββββββββββββββββββββββββββββββββββββ
β Main Content β Layer 1 (back)
βββββββββββββββββββββββββββββββββββββββββββββ
GPU composites these layers together each frame!
Change in one layer = Only that layer updates!
Layer Memory Cost
Layer size: 1920 Γ 1080 pixels Color depth: 4 bytes per pixel (RGBA) Memory = 1920 Γ 1080 Γ 4 = 8,294,400 bytes β 8 MB per full-screen layer! βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β οΈ LAYER EXPLOSION - Common Problem: Problematic code: /* Every list item gets its own layer! */ .list-item { will-change: transform; /* Creates layer! */ } If you have 1000 list items = 1000 layers! = Huge GPU memory usage = Slow compositing = Possible crashes on mobile! βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β Better approach: /* Only promote when needed */ .list-item:hover { will-change: transform; /* Layer only on hover! */ } .list-item.animating { will-change: transform; /* Layer only during animation! */ }
DO: Apply will-change before animation starts, remove after animation ends.
DON'T: Apply will-change to everything or keep it permanently on static elements.
element.addEventListener('transitionend', () => element.style.willChange = 'auto');
The Compositing Process
STEP 1: Collect Layer Information βββββββββββββββββββββββββββββββββββ For each layer, compositor knows: β’ texture: GPU_TEXTURE_ID // Reference to painted bitmap β’ position: { x: 100, y: 50 } // Screen position β’ transform: matrix3d(...) // Any transforms applied β’ opacity: 0.9 // Transparency β’ zIndex: 3 // Stacking order STEP 2: Sort Layers by Z-Order βββββββββββββββββββββββββββββββββ 1. Background layer (z: 0) 2. Content layer (z: 1) 3. Fixed header (z: 100) 4. Modal overlay (z: 1000) 5. Tooltip (z: 9999) STEP 3: Draw Layers (Back to Front) βββββββββββββββββββββββββββββββββββββ GPU Framebuffer: Start: Empty βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β (empty) β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ Draw Layer 1 (background): βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ Draw Layer 2 (content) - blended on top: βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ Draw Layer 3 (header) with transform: βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ Final: Complete frame ready for display! STEP 4: Display (Swap Buffers) βββββββββββββββββββββββββββββββββ GPU uses double/triple buffering - swap to display at vsync
The Complete Rendering Pipeline
Now let's see how ALL the pieces fit together β from the user typing a URL to pixels appearing on screen!
USER TYPES: https://example.com βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ PHASE 1: NETWORK βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ β DNS ββββββΆβ TCP ββββββΆβ TLS β β Lookup β β Handshake β β Handshake β β (~20-120ms)β β (~40ms) β β (~40-100ms)β ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ β β βΌ βΌ "What's the IP?" "Connection secure!" β βΌ ββββββββββββββββββββββββββββββββββ β HTTP Request/Response β β "GET /index.html" β β ββββββββΆ β β ββββββββ HTML bytes β ββββββββββββββββββββββββββββββββββ βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ PHASE 2: PARSING (Parallel Processes) βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ HTML Stream arrives: <html><head><link href="style.css"><script src="app.js">... β βββββββββββββββββββββββΌββββββββββββββββββββββ β β β βΌ βΌ βΌ ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ β HTML Parser β β CSS Parser β β Preload β β β β β β Scanner β β Tokenize β β Parse CSS β β β β Build DOM β β Build CSSOM β β Find more β β β β β β resources! β ββββββββ¬ββββββββ ββββββββ¬ββββββββ ββββββββ¬ββββββββ β β β βΌ βΌ βΌ ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ β β β β β Fetch: β β DOM TREE β β CSSOM β β - images β β β β β β - scripts β β document β β StyleSheets β β - fonts β β ββhtml β β ββrules β β (parallel!) β β ββbody β β ββstyles β β β ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ β β β BLOCKED BY: β β ββββββββββββββββ CSS (render blocking) β <script> (parser blocking) β β βββββββββββ¬βββββββββββ β βΌ Wait for BOTH to be ready... βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ PHASE 3: RENDER TREE CONSTRUCTION βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ ββββββββββββββββ ββββββββββββββββ β DOM TREE β + β CSSOM β ββββββββ¬ββββββββ ββββββββ¬ββββββββ β β ββββββββββββ¬ββββββββββββββ β βΌ ββββββββββββββββββββββ β STYLE CALC β β β β For each DOM node: β β - Find matching β β CSS rules β β - Apply cascade β β - Resolve inherit β β - Compute values β βββββββββββ¬βββββββββββ β βΌ ββββββββββββββββββββββ β RENDER TREE β β β β Only VISIBLE nodes β β + computed styles β β β β (display:none β β elements removed) β βββββββββββ¬βββββββββββ β βΌ βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ PHASE 4: LAYOUT βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ ββββββββββββββββββββββ β LAYOUT β β (Reflow) β β β β Calculate: β β β’ Exact positions β β β’ Exact sizes β β β β Convert: β β %, em, vh β pixels β β auto β calculated β βββββββββββ¬βββββββββββ β βΌ ββββββββββββββββββββββ β LAYOUT TREE β β β β Each node has: β β x: 100 β β y: 50 β β width: 400 β β height: 200 β βββββββββββ¬βββββββββββ β βΌ βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ PHASE 5: PAINT βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ ββββββββββββββββββββββ β PAINT β β β β Generate drawing β β commands: β β β’ DrawRect(...) β β β’ DrawText(...) β β β’ DrawImage(...) β β β β Create paint β β layers for: β β β’ transform β β β’ opacity β β β’ position:fixed β βββββββββββ¬βββββββββββ β βΌ ββββββββββββββββββββββ β PAINT RECORDS β β per layer β β β β Layer 1: [...] β β Layer 2: [...] β β Layer 3: [...] β βββββββββββ¬βββββββββββ β βΌ βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ PHASE 6: RASTERIZATION βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ ββββββββββββββββββββββ β RASTERIZATION β β β β Convert paint β β records to pixels β β (bitmaps/textures) β β β β Done on GPU or β β raster threads β βββββββββββ¬βββββββββββ β βΌ ββββββββββββββββββββββ β LAYER TEXTURES β β in GPU memory β β β β [ββββββββββββββββ] β β [ββββββββββββββββ] β β [ββββββββββββββββ] β βββββββββββ¬βββββββββββ β βΌ βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ PHASE 7: COMPOSITING βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ ββββββββββββββββββββββ β COMPOSITOR β β (Separate Thread) β β β β Combine layers: β β β’ Apply transforms β β β’ Apply opacity β β β’ Handle z-order β β β’ Blend together β βββββββββββ¬βββββββββββ β βΌ ββββββββββββββββββββββ β FINAL FRAME β β sent to GPU β β β β ββββββββββββββββββ β β ββββββββββββββββββ β β βββ YOUR PAGE ββββ β β ββββββββββββββββββ β β ββββββββββββββββββ β βββββββββββ¬βββββββββββ β βΌ ββββββββββββββββββββββ β DISPLAY! β β (60fps = 16.6ms β β per frame) β ββββββββββββββββββββββ
Time βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββΆ NETWORK: β DNS β TCP β TLS βββββββββ HTML Download ββββββββββ β β β β βββ CSS βββ β β β β β ββββββββββββ JS βββββββββββ β β β β βββββββ Images ββββββββββββββββββββββββββββ MAIN THREAD: β β HTML Parse β Style β Layout β Paint β β β βββββββββββββββββββββββββββββββββββββββ β β β DOM β β β β β β β ββblockedββΆβ β β β β β β (by JS/CSS)β β β β β COMPOSITOR THREAD: β β Composite ββββββββββΆ β βββββββββββββ β β β KEY METRICS: β β βββ TTFB (Time to First Byte) β β β ββββββββββββ FCP (First Contentful Paint) β β β βββββββββββββββββββββββ LCP (Largest Contentful Paint) β β β BLOCKING POINTS: β’ CSS blocks rendering (render-blocking) β’ <script> blocks parsing (parser-blocking) β’ JavaScript on main thread blocks everything else!
When you change something, what work does the browser do? βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ CHANGE WIDTH/HEIGHT/MARGIN (Geometry Changes) βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ [Style] β [Layout] β [Paint] β [Composite] β Most expensive! Recalculates positions for potentially many elements. Avoid in animations! βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ CHANGE COLOR/BACKGROUND/SHADOW (Visual-only Changes) βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ [Style] β β [Paint] β [Composite] β Medium cost. Skips layout but still repaints affected area. βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ CHANGE TRANSFORM/OPACITY (Compositor-only Changes) βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ [Style] β β β [Composite] β Cheapest! GPU just moves/blends existing textures. Can run at 60fps even with heavy JavaScript! βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ VISUAL COMPARISON: .move-with-left { .move-with-transform { left: 100px; // BAD! transform: translateX(100px); // GOOD! } } Left animation: Transform animation: ββββββ¬βββββ¬βββββ¬βββββ¬βββββ ββββββ¬βββββ¬βββββ¬βββββ¬βββββ β L β P β C β L β P β β C β C β C β C β C β ββββββ΄βββββ΄βββββ΄βββββ΄βββββ ββββββ΄βββββ΄βββββ΄βββββ΄βββββ Layout every frame! Composite only! Much faster!
Frame Budget
For smooth 60fps animations, each frame must complete in 16.6ms (1000ms Γ· 60 frames).
Frame breakdown:
β’ JavaScript: ~10ms max
β’ Style + Layout + Paint + Composite: ~6ms
If any frame takes longer, you get "jank" (visible stutter)!
Optimization Best Practices
Animation: Right vs Wrong
.box {
position: absolute;
left: 0;
transition: left 0.3s;
}
.box:hover {
left: 100px; /* Triggers layout! */
}
.box {
transform: translateX(0);
transition: transform 0.3s;
}
.box:hover {
transform: translateX(100px);
/* Compositor only! */
}
Summary
π― The Complete Pipeline
HTML β DOM β CSSOM β Render Tree β Layout β Paint β Composite β Screen Change width: [Style] β [Layout] β [Paint] β [Composite] Change color: [Style] β β [Paint] β [Composite] Change transform: [Style] β β β [Composite] Less work = Faster! Transform/opacity skip the most steps!
Key Takeaways
| Stage | What to Optimize | Key Techniques |
|---|---|---|
| Network | Reduce latency | Preconnect, HTTP/2, CDN |
| Parsing | Don't block parser | async/defer scripts, inline critical CSS |
| Styles | Reduce complexity | Simple selectors, avoid * |
| Layout | Avoid thrashing | Batch reads/writes, use transform |
| Paint | Reduce area/complexity | Promote layers, avoid shadows |
| Composite | Use GPU | transform, opacity only |
You now understand the complete browser rendering pipeline β from DNS resolution to compositing. Use this knowledge to build faster, more performant web applications!