The three-stage pipeline, named
Every frame the browser runs a pipeline: style, layout, paint, composite. Style resolves your CSS. Layout computes geometry — where every box sits, and it is the expensive one because one change can cascade through the whole document. Paint turns boxes into pixels. Composite stitches the layers together on the GPU.
Animate width and the browser must redo layout (everything around the element moves), then repaint, then composite. Animate transform and the browser skips straight to composite: the element's pixels are already painted on their own layer, and the GPU just moves that texture. Same for opacity, which only blends layers. That is the whole secret — those two properties are cheap because they are the only ones that never invalidate the earlier stages.
Animating width / height / top / left:
style -> LAYOUT -> PAINT -> composite (all three, every frame)
Animating transform / opacity:
style -> composite (layout + paint skipped)The traps that sneak layout back in
- Animating transform, then reading offsetWidth in the same frame: the read forces a synchronous layout flush — the 'layout thrash' that silently defeats the compositor.
- A transform animation on an element whose ancestor resizes in the same frame: the ancestor's layout change re-lays-out the transformed element's layer anyway.
- filter, box-shadow, and border-radius animate on the main thread — shadow and radius changes repaint the layer every frame. A moving card with a big blur shadow is a paint-heavy card.
- clip-path and mask are compositor-adjacent but vary wildly by browser; test before trusting.
- Content inside a transformed element that reflows (text wrapping, image decode) forces the layer to repaint mid-animation.
Move with transform. Fade with opacity. Keep shadows and gradients still while things move. If an element must move AND glow, put the glow on a separate still layer beneath the moving one — the GPU composites the pair for the price of one moving texture.
How to check you actually got the fast path
Open the performance panel, record the animation, and look for green 'Layer tree' activity with no tall yellow layout blocks. Then open the rendering panel and turn on 'Layer borders': composited layers show a border, and a healthy animation shows the moving element on its own layer while the page around it stays still. If the whole page repaints every frame, the animation did not make it to the GPU — it is doing layout and paint, and no amount of easing will make that smooth.