Tags: web-dev concept

Compositing and Layers

Date: 2026-08-16


The browser can split the page into separate images, hand them to the GPU, and move them around without redrawing anything. That’s why transform is cheap — and why promoting everything to its own layer trades a rendering problem for a memory one.


What it is

Compositing is the final stage of The Rendering Pipeline: taking painted surfaces — layers — and assembling them into the frame the user sees.

Once an element has its own layer, moving, scaling, rotating or fading it requires no repainting at all. The GPU redraws the same bitmap in a different place.

WITHOUT ITS OWN LAYER              WITH ITS OWN LAYER

move the element                   move the element
  → layout                           → composite only
  → paint                            (GPU repositions an existing bitmap)
  → composite
  every frame                      every frame, near-free

What gets promoted

The browser decides, using heuristics that vary between engines and versions. Common triggers:

  • transform: translateZ(0) or translate3d() — the historical hack
  • will-change: transform or will-change: opacity — the sanctioned modern way to ask
  • position: fixed
  • <video>, <canvas>, animated WebGL
  • An element with a running transform or opacity animation
  • An element overlapping one that’s already promoted — see below

[CHECK: promotion heuristics differ by engine and change between versions — verify against current DevTools layer output rather than a list.]

The memory cost

A layer is a bitmap held in GPU memory. Its size is roughly:

width × height × 4 bytes (RGBA)

full-screen layer on a 1440 × 900 display at device pixel ratio (DPR) 2
  = 2880 × 1800 × 4
  ≈ 20 MB   per layer

Ten full-screen promoted layers is 200MB of GPU memory, which on a mid-range Android phone is enough to cause the browser to discard layers, thrash, or crash the tab.

In plain terms: each promoted layer is a full-resolution screenshot held in memory. A handful is fine; promoting everything is how a page becomes smooth on your laptop and unusable on a customer’s phone.

Layer explosion

The failure mode nobody expects. An element that overlaps a promoted layer usually gets promoted too, to preserve correct stacking order. Promote one thing carelessly and everything painted above it follows.

promote a fixed header      →  1 layer
...which overlaps a banner  →  2 layers
...which overlaps the grid  →  3 layers
...each product card        →  n layers

The symptom is a page that gets progressively less smooth as it grows, with no single change responsible. DevTools’ Layers panel shows the count and the memory, and the reason each layer exists — it’s the only reliable way to diagnose this.

Using it properly

  • Promote only what animates, and only while it animates
  • Set will-change shortly before, remove it after. Leaving it on permanently keeps the memory allocated forever:
    el.style.willChange = 'transform';
    requestAnimationFrame(() => { /* animate */ });
    // on animation end:
    el.style.willChange = 'auto';
  • Don’t use translateZ(0) as a blanket fix. It was a workaround for engines that no longer need it, and applying it broadly is the main cause of layer explosion
  • Keep promoted layers small. A promoted element the size of a button costs a few KB; the same treatment on a full-width section costs megabytes
  • Check on a real mid-range Android device, not a desktop. GPU memory is where the difference is starkest

Where it connects