Browser Painting and Composition

Written by Backbone Tutorials Team

Last updated: June 2026 · 10 min read

After layout has placed every element, the browser still has to fill those boxes with actual pixels. That is painting, and it runs on the CPU. The compositor then assembles those painted pieces into the frame you see, and it can do parts of that work on the GPU, off the main thread. The split between these two stages explains why some animations are smooth and others stutter, why transform and opacity behave differently from width and background-color, and why isolating frequently-changing elements into their own layers is a genuine optimisation rather than a cargo-cult trick.

This guide concludes the low-level half of the rendering cluster and pairs with the browser rendering pipeline overview and the practical optimisations in the rendering lifecycle.

Paint and composite split Layout runs on the main thread, then paint rasterizes layers on the CPU, and the compositor assembles the frame on the GPU off the main thread. Main thread Layout Paint (CPU) rasterize layers Composite (GPU, off main thread) position · blend · transform · opacity Compositor-only change (no paint needed) transform / opacity change → skip paint compositor applies directly → smooth
Paint rasterizes on the CPU; the compositor assembles the frame on the GPU. Transform and opacity skip paint entirely.

What painting costs

Paint is the stage where geometry becomes pixels, and its cost is easy to underestimate because the browser hides most of the work from the main thread timeline.

Rasterization: area times complexity

The browser walks the render tree and fills each box with the pixels its style demands: flat colour, gradient, image, text glyph, border, shadow, filter. The cost scales with two factors. The first is area: painting a 1200px-wide hero image costs more than a 100px icon because there are twelve times as many pixels to fill. The second is complexity: a simple flat background is cheap; a blurred backdrop-filter or a layered box-shadow is expensive, because every pixel requires more computation. A visually rich page with many large shadows that repaints on every scroll event will miss the frame budget regardless of how clean the layout work is.

Compositor layers

The compositor does not work with the whole page as one flat image. It works with layers, and the distinction matters enormously for animation performance.

Layers as independent bitmaps

The browser splits the page into compositor layers: independently painted bitmaps that the compositor can position, transform, and blend without touching the CPU paint pipeline. The scrolling layer, fixed-position elements, and elements with 3D transforms each get their own layer automatically. The key property is that once a layer is painted, the compositor can manipulate it without repainting. Scrolling a page with a compositor-managed scroll layer means the GPU moves the bitmap; nothing repaints. An opacity fade on a promoted element is the GPU scaling an alpha value on an existing bitmap. No CPU time, no paint pass, no involvement from the main thread.

Promoting elements with will-change

When you know an element is about to animate, you can tell the browser to prepare its layer in advance, avoiding a jank spike on the first frame.

Using will-change correctly

/* Tell the browser this element will animate transform soon */
.slide-panel {
  will-change: transform;
}

/* Remove the hint when animation is done to free GPU memory */
.slide-panel.is-done {
  will-change: auto;
}

will-change: transform signals the browser to promote the element to its own compositor layer before the animation starts. Without it, the first animated frame may stutter as the browser creates the layer on the fly. The cost is GPU memory: each promoted layer occupies texture memory, and over-using will-change on elements that are not actually animating drains it for nothing. The pattern is to add it just before an animation is triggered and remove it when the animation ends, keeping the memory cost short-lived. Do not apply it to every element as a blanket optimisation.

GPU-accelerated compositing

The GPU's strength is applying the same transformation to many pixels in parallel. Compositing exploits exactly that.

transform and opacity as free animations

/* Both of these run on the compositor, off the main thread */
.card {
  transition: transform 0.25s ease, opacity 0.25s ease;
}
.card:hover {
  transform: translateY(-4px) scale(1.02);
  opacity: 0.92;
}

transform and opacity are the two CSS properties that the compositor can apply to an already-painted layer without involving the main thread at all. Moving a card upward with translateY tells the GPU to reposition the layer's bitmap; changing opacity tells it to scale the layer's alpha channel. Both happen in a dedicated compositor thread, so they remain smooth even when the main thread is busy processing JavaScript. Animating top, left, width, or background-color instead triggers layout or paint on every frame, dragging the main thread back in. The rule is concrete: animate transform and opacity; for everything else, question whether the animation is necessary.

Paint storms and large invalidations

A paint storm is when a large region of the page is marked dirty and repaints on every frame, usually because a visually complex element is changing in a way that forces the CPU through the full rasterization pipeline repeatedly.

Isolating the changing element

/* Isolate a frequently-changing element to its own layer */
.live-ticker {
  will-change: contents;  /* or transform: translateZ(0) as fallback */
  contain: strict;        /* limit style/layout scope to this subtree */
}

Common triggers are animated gradients, large blurred backgrounds that update, or canvas elements drawing at 60fps whose containing element has complex neighbours. The fix has two parts. First, isolate the changing element into its own compositor layer so repaints are confined to its region rather than invalidating the whole page. Second, use contain: strict or contain: layout paint to tell the browser that changes inside the element cannot affect anything outside it, allowing style and layout to skip the rest of the page entirely. Together these contain both the paint cost and the layout cost of a high-frequency update.

Measuring paint in DevTools

Paint is invisible without the right tools. Both the occurrence and the cost can be instrumented precisely.

Paint flashing and the Layers panel

Enable Paint Flashing in Chrome DevTools Rendering settings and scroll or interact with the page. Green rectangles appear wherever the browser repaints. Large green flashes that cover most of the viewport on every scroll indicate a paint storm. The Layers panel shows every compositor layer, its memory cost in kilobytes, and the reason the browser promoted it. Unexpected layers with high memory footprints are candidates for will-change: auto to demote them. The Performance panel's flame chart shows green paint blocks; their width is the CPU time consumed. Any green block wider than a few milliseconds on a complex page warrants investigation before moving to the capstone render optimisation guide.

Frequently Asked Questions

What is browser painting?

Browser painting, also called rasterization, is the stage where the browser fills computed boxes with actual pixels. It processes text, backgrounds, borders, images, and shadows into bitmaps. Paint cost scales with the area repainted and the complexity of effects like blur and shadows.

What is a compositor layer?

A compositor layer is a bitmap region that is painted once and then handled separately by the compositor, often on the GPU. Transforming or changing the opacity of a compositor layer does not require repainting; the compositor applies the change to the existing bitmap, which is why transform and opacity animations are smooth even on a busy main thread.

What does will-change do?

will-change tells the browser which CSS properties an element is about to animate, giving it time to promote the element to its own compositor layer before the animation starts. This avoids a janky first frame while the browser scrambles to create the layer. Use it sparingly on elements you know will animate.

What is a paint storm?

A paint storm is when a large area of the page is invalidated and must be repainted on every frame. It typically happens when a visually complex background, a large shadow, or a filter-heavy element changes frequently. The fix is either to isolate the changing element in its own compositor layer or to reduce the painted area.

Read next: the Rendering hub, or revisit the browser rendering pipeline to see how paint fits into the full frame sequence.

Want smooth animation without GPU memory waste?

The Backbone guide shows view transitions built on compositor-safe properties.

Explore the Backbone Guide →