Layout, paint, composite: why transform is cheap and top is not
·2 min read ·Performance · Browser · CSS
Every pixel your browser puts on screen goes through the same pipeline: style, layout, paint, composite. Most developers never think about it until an animation stutters. Then it's worth knowing exactly which stage you're paying for.
The four stages
Style figures out which CSS rules apply. Layout computes geometry—where every box sits and how big it is. Paint fills in pixels: text, colors, borders, shadows. Composite takes the painted layers and stitches them together, usually on the GPU.
The stages cascade. Change an element's width and the browser has to redo layout, then repaint, then composite. Change its background-color and layout survives, but paint runs again. Change its transform or opacity and—on its own layer—the browser skips straight to composite.
That's the whole trick. It's not that transforms are magically fast. It's that they let the browser skip the expensive stages.
Why layout hurts
Layout is rarely local. Move one element with top and the browser may recompute geometry for a large chunk of the tree, because boxes affect their neighbors. Do that every frame, on a page with a real DOM, and you blow your frame budget. At 60fps you have about 16ms per frame, and that budget also has to cover your JavaScript.
/* Layout + paint + composite, every frame */
.bad { animation: slide-top 300ms ease-out; } /* animates top */
/* Compositor only */
.good { animation: slide 300ms ease-out; } /* animates transform */
Working on Redblock, my browser aim trainer, is where this stopped being theory for me. When a game loop shares the main thread with the UI, every unnecessary layout is a hitch the player feels.
The exceptions
Compositing isn't free either. Every layer costs GPU memory, and slapping will-change on everything is a classic way to trade jank for crashes on low-end phones. Promote the few elements that actually animate, and remove the hint when the animation is done.
The rule of thumb is simple: if it moves, it moves with transform. If it fades, it fades with opacity. Everything else is a decision you should be able to defend with the DevTools performance panel open.