Home/Learn/JavaScript & TypeScript/How the Browser Renders — and Why Your Page Janks

How the Browser Renders — and Why Your Page Janks

Advanced
The Browser

Style, layout, paint, composite. Knowing which CSS properties skip layout is the difference between a smooth animation and a stuttering one.

Overview

Every frame the browser does the same sequence: recalculate styles, compute layout, paint pixels, composite layers. At 60fps that is 16.7ms per frame for everything including your JavaScript. Changing a property that forces layout means the whole sequence runs; changing one that only composites can be handled by the GPU, skipping layout and paint entirely. That single distinction explains why animating transform is smooth and animating top is not, and it is the most practically useful performance knowledge in frontend work.

The Pipeline

Four stages. Which ones run depends on what you changed.

transform and opacity are free; top and left are not
// Style -> Layout -> Paint -> Composite

// Changes LAYOUT (all four stages) — expensive
width, height, top, left, margin, padding, font-size, display

// Changes PAINT only (skips layout)
color, background-color, box-shadow, border-radius, visibility

// COMPOSITE only (GPU, skips layout and paint) — cheapest
transform, opacity

// So this janks:
el.style.left = x + 'px'

// And this does not:
el.style.transform = `translateX(${x}px)`

// Hint to the browser before an animation starts:
.card { will-change: transform }   // use sparingly; it costs memory

The Frame Budget

16.7ms per frame at 60fps, shared between your JavaScript and the browser's work. Long tasks are what users feel.

rAF, and measuring long tasks
// requestAnimationFrame runs just before the next paint
function animate() {
  el.style.transform = `translateX(${x}px)`
  x += 2
  requestAnimationFrame(animate)
}
requestAnimationFrame(animate)

// Never animate with setInterval — it is not synced to paint
// and will tear or drop frames.

// Anything over ~50ms on the main thread is a "long task" and
// blocks input. Measure them:
new PerformanceObserver(list => {
  for (const entry of list.getEntries()) console.warn('long task', entry.duration)
}).observe({ entryTypes: ['longtask'] })

What Users Actually Measure

Core Web Vitals are the three numbers Google ranks on and users feel.

LCP, INP, CLS and their usual causes
// LCP — Largest Contentful Paint: when the main content appears (< 2.5s)
//   Fixes: server-render, preload the hero image, cut render-blocking CSS/JS

// INP — Interaction to Next Paint: responsiveness to clicks (< 200ms)
//   Fixes: break up long tasks, defer non-urgent work, avoid huge re-renders

// CLS — Cumulative Layout Shift: how much things jump (< 0.1)
//   Fixes: width/height on images, reserve space for ads and banners,
//          never insert content above what the user is reading

// Reading them in the field:
new PerformanceObserver(list => {
  for (const e of list.getEntries()) send(e.name, e.value)
}).observe({ type: 'largest-contentful-paint', buffered: true })

Key Points to Remember

  • 1The pipeline is style, layout, paint, composite — what you change decides how many stages run
  • 2transform and opacity composite on the GPU; top, left, width and height force a full layout
  • 3A frame is 16.7ms at 60fps, shared with the browser — anything over 50ms on the main thread blocks input
  • 4requestAnimationFrame syncs to paint; setInterval for animation drops frames
  • 5CLS is usually fixed by giving images explicit dimensions so space is reserved before they load

Interview Questions

Sign in to ask Aria
1

Why is animating transform smoother than animating left?

Medium
2

What are the four stages of the browser rendering pipeline?

Medium
3

What causes Cumulative Layout Shift and how do you prevent it?

Medium

Ask Aria about How the Browser Renders — and Why Your Page Janks

Your personal AI tutor — ask anything about this concept

Revision Status

Personal Notes

Sign in to save personal notes for this topic.

Discussion

Sign in to join the discussion.

Loading discussion…