Every pixel you see on screen goes through a precise pipeline. Understanding it is crucial for building performant UIs at Google-scale.
The Critical Rendering Path
- Parse HTML → DOM Tree
- Parse CSS → CSSOM Tree
- Combine → Render Tree (only visible elements)
- Layout → Calculate geometry (position, size)
- Paint → Fill in pixels (colors, borders, shadows)
- Composite → Layer composition on GPU
Layout (Reflow) vs Paint (Repaint)
Layout is triggered when geometry changes: width, height, position, font-size. It's expensive because it affects descendants.
Paint is triggered by visual changes that don't affect geometry: color, visibility, background-image.
The Worst Offenders
// Forces synchronous layout (layout thrashing)
elements.forEach(el => {
el.style.width = container.offsetWidth + "px"; // Read then write in loop!
});
// Better: batch reads, then writes
const width = container.offsetWidth;
elements.forEach(el => {
el.style.width = width + "px";
});Compositing: The GPU Fast Lane
Properties like transform and opacity skip layout and paint entirely — they go straight to the compositor thread on the GPU.
/* Bad - triggers layout */
.animate { left: 100px; }
/* Good - compositor only */
.animate { transform: translateX(100px); }will-change: Use Wisely
will-change promotes an element to its own compositor layer. But each layer costs GPU memory. At Google, we only use it for elements that will actually animate.
Production Tips
- Use Chrome DevTools Performance tab to identify layout thrashing
- Avoid reading layout properties inside animation frames
- Prefer
requestAnimationFrameoversetTimeoutfor visual updates - Use
contain: layoutto limit reflow scope