The browser turns CSS changes into pixels through a pipeline, and different changes trigger different (more or less expensive) stages:
Style → Layout (reflow) → Paint → Composite
- Layout / reflow — recalculating geometry (positions/sizes). Most expensive — changing one element can force recalculating many others.
- Paint / repaint — filling in pixels (colors, text, shadows). Cheaper than layout.
- Composite — combining already-painted layers (GPU). Cheapest.
Which properties trigger what
/* ❌ trigger LAYOUT (reflow) — costly, recalculates geometry every frame */
width, height, top, left, margin, padding, font-size
/* ⚠️ trigger PAINT — repaint pixels */
color, background, box-shadow, border-radius
/* ✅ trigger only COMPOSITE — GPU, no layout/paint */
transform, opacity
This is the single most important performance rule: animate transform and opacity, not width/top/margin. Animating layout properties re-runs layout on every frame (jank); transforms are composited by the GPU (smooth 60fps).
/* ❌ janky */ @keyframes a { to { left: 300px; width: 200px; } }
/* ✅ smooth */ @keyframes b { to { transform: translateX(300px) scaleX(2); } }
Hint the browser with will-change (sparingly)
.animated { will-change: transform; } /* promotes to its own GPU layer ahead of time */
Use will-change only on elements about to animate — overusing it wastes memory by creating too many layers.
Avoid layout thrashing in JS
// ❌ read-write-read-write forces multiple synchronous reflows
for (const el of els) { el.style.width = el.offsetWidth + 10 + "px"; }
// ✅ batch reads, then writes
const widths = els.map(el => el.offsetWidth);
els.forEach((el, i) => el.style.width = widths[i] + 10 + "px");
Other wins
- contain: layout/paint — isolate a subtree so changes don't reflow the whole page
- content-visibility: auto — skip rendering offscreen content
- minimize deep selectors and huge unused stylesheets
Why it matters
Rendering performance is mostly about not triggering layout repeatedly.
Knowing that transform/opacity are composite-only (cheap) while width/top/margin force reflow (expensive) — plus avoiding layout thrashing and using contain/content-visibility — is what keeps animations at 60fps and pages feeling fast.
