Browser Compositing Layers

TL;DR / The browser separates certain elements into independent GPU-backed layers so that changes to one layer (like an animation) do not require repainting the entire page.

How It Works

 Without layers               With compositing layers

 ┌──────────────┐              ┌───────────────┐
 │  Full page   │              │    Layer 1    │ only changed
 │   repaint    │ every frame  │ (background)  │ layer repaints
 │              │              │               │
 └──────────────┘              └───────────────┘


                               ┌───────────────┐
                               │    Layer 2    │ GPU composites
                               │  (animated)   │ layers together
                               │               │
                               └───────────────┘


                               ┌───────────────┐
                               │    Layer 3    │
                               │  (fixed nav)  │
                               │               │
                               └───────────────┘

Edit diagram

When the browser paints a page, it doesn't necessarily paint everything onto a single surface. It can divide the page into multiple compositing layers — independent bitmap textures that are uploaded to the GPU and composited (stacked and blended) together to produce the final image. This separation means changes to one layer (moving, rotating, fading) can be handled entirely by the GPU compositor without touching the main thread's layout or paint pipeline.

How Layers Are Created

The browser promotes elements to their own compositing layer when certain conditions are met:

  • 3D or perspective transforms: transform: translate3d(), transform: translateZ(), perspective
  • Animated transforms or opacity: When the browser detects these properties will animate (via CSS transitions, animations, or will-change)
  • will-change: transform or will-change: opacity: Explicit hints to the browser to promote the element
  • Fixed or sticky positioning: position: fixed elements often get their own layer because they move independently of scrolling content
  • Overlapping composited elements: If an element overlaps another composited layer, the browser may promote it to preserve correct stacking order (implicit compositing)
  • Hardware-accelerated video and canvas: <video>, <canvas>, and WebGL content typically get dedicated layers

The Compositing Pipeline

Once the page is divided into layers, the rendering pipeline becomes:

  1. Layout — determine element positions and sizes (main thread)
  2. Paint — generate draw commands for each layer (main thread)
  3. Rasterize — convert draw commands into bitmaps, potentially on GPU threads (compositor thread)
  4. Composite — position and blend layer bitmaps into the final frame (GPU, off main thread)

The key optimization: steps 3 and 4 can happen off the main thread. When you animate transform or opacity on a composited layer, only the composite step runs — no layout, no paint, no main thread work. This is why GPU-accelerated animations achieve 60fps even while JavaScript is running.

Layer Memory Cost

Each compositing layer consumes GPU memory proportional to its pixel area. A full-screen layer on a 1920x1080 display at 2x DPI requires approximately 16MB of GPU memory (3840 x 2160 pixels x 4 bytes RGBA). Promoting too many elements to layers can exhaust GPU memory, causing the browser to fall back to software rendering or triggering compositing thrash (repeatedly creating and destroying layers).

This is why blanket use of transform: translateZ(0) or will-change: transform on many elements is counterproductive. Each promotion allocates GPU memory and adds compositing overhead. The benefit of layer promotion must outweigh the memory cost.

Implicit Layer Promotion

One of the more surprising behaviors is implicit compositing. If element A is on a compositing layer and element B overlaps A with a higher z-index, the browser must promote B to its own layer to maintain correct paint order. This can cascade — if B is promoted, C which overlaps B may also be promoted, and so on.

This cascade can create dozens of unexpected layers, ballooning memory usage. Chrome DevTools' Layers panel shows all compositing layers, their sizes, and why each was created. If you see layers with reasons like "composited because of overlap," you may need to adjust z-index stacking or element positions.

Layer Squashing

To mitigate the implicit compositing problem, browsers employ layer squashing — merging multiple implicitly composited elements into a single layer when doing so does not affect visual output. This reduces memory usage but is not always possible (e.g., when elements have different transform or opacity animations).

Debugging Layers

Chrome DevTools provides multiple tools for layer inspection:

  • Layers panel: Shows all compositing layers in a 3D view, with size, memory, and compositing reason for each
  • Rendering tab > "Layer borders": Draws borders around compositing layers directly on the page
  • Performance tab: Shows composite events in the timeline, helping identify compositing bottlenecks

Gotchas

  • will-change should be applied dynamically, not statically. Apply it just before an animation starts and remove it after the animation ends. Keeping will-change on many elements permanently wastes GPU memory.
  • Not all CSS properties benefit from layer promotion. Only transform and opacity can be animated by the compositor without main thread involvement. Animating background-color, box-shadow, or border-radius still triggers paint even on a composited layer.
  • Mobile GPUs have limited memory. Over-promoting layers on mobile devices can cause severe performance degradation or crashes. Test on real devices, not just desktop browsers.
  • position: fixed does not guarantee layer promotion in all browsers and situations. Combine with will-change: transform if you need to ensure promotion.
  • Compositing happens after paint. If your element triggers layout or paint on every frame (e.g., animating width), having it on a separate layer only helps with the compositing step — layout and paint still run on the main thread.