The rendering pipeline and performance

CSS · Expert · 15 min

Smooth scrolling and animation depend on knowing which CSS changes are cheap and which redo the whole page.

After parsing, the browser turns DOM plus CSSOM into pixels in stages:

  • Style: compute the final value of every property for every element (selector matching, cascade, inheritance).
  • Layout (reflow): compute each box's size and position.
  • Paint: record drawing commands for text, colours, borders and shadows, split into layers.
  • Composite: the GPU combines layers into the final frame.

A change only redoes the stages from where it enters. Changing width, font-size or margin triggers layout, paint and composite. Changing color or background skips layout. Changing transform or opacity on an element with its own layer can skip straight to composite, which is why animations should use them.

Layout thrashing happens when JavaScript interleaves writes and reads, such as setting a style and then reading offsetHeight in a loop. Each read forces a synchronous layout. Batch reads before writes, or use requestAnimationFrame.

Tools for containing the cost:

  • will-change: transform hints that an element will animate so the browser can promote it to a layer ahead of time. Use it sparingly; every layer costs memory.
  • contain: layout paint (or content) promises the subtree doesn't affect outside layout, limiting how much is redone.
  • content-visibility: auto skips rendering off-screen sections entirely; pair it with contain-intrinsic-size to keep scrollbars stable.

Selector matching is rarely the bottleneck today; large DOMs and forced layouts usually are. Profile with the Performance panel, which colours style, layout, paint and composite work separately.

Example

/* cheap: composite-only animation */
.drawer { translate: -100% 0; transition: translate 250ms ease; will-change: translate; }
.drawer.open { translate: 0 0; }

/* expensive: animates layout every frame */
.drawer-bad { left: -300px; transition: left 250ms; }

/* long page: skip rendering sections until they near the viewport */
.article-section {
  content-visibility: auto;
  contain-intrinsic-size: auto 800px;   /* placeholder height for scrollbars */
}

/* widget whose internals never affect outside layout */
.chat-widget { contain: content; }

Remember

  • Pipeline: style, layout, paint, composite
  • transform and opacity animations can skip layout and paint
  • Interleaving style writes and layout reads causes thrashing
  • content-visibility: auto skips off-screen rendering

Watch out for

  • Putting will-change on many elements permanently, wasting GPU memory.

Check yourself

  1. Changing which property most likely triggers layout?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: developer.mozilla.org/en-US/docs/Web/Performance/Guides/How_browsers_work