Use Composited Animations
Animate only transform and opacity so animations run on the GPU and never block the main thread.
Metrics
This technique does not move LCP.
This technique can move up to 67% of TBT's share of the Lighthouse score.
This technique can move up to 67% of CLS's share of the Lighthouse score.
This technique does not move FCP.
This technique does not move SI.
This technique does not move TTFB.
Overview
The browser compositing pipeline separates work between the main thread (layout, paint) and the compositor thread (GPU compositing). Animations that change transform or opacity run entirely on the compositor thread — they never block JavaScript or input handling, and never trigger layout or repaint. Animating any other CSS property (width, height, top, left, margin, padding) forces the browser back to the main thread on every frame, consuming 6–16 ms per frame that should be going to user interactions.
Impact grade, A+ to D: how much of the Lighthouse score this technique can move. Unaffected: LCP, FCP, SI, TTFB.
The Problem
A CSS animation that changes width from 0 to 100% runs 60 layout-and-paint cycles per second. Each cycle takes 6–16 ms on the main thread — the same thread handling JavaScript, user input, and INP measurement. On a page with 3 simultaneously animating elements using layout properties, 30–50% of the main thread’s frame budget is consumed by animation, leaving insufficient time for event handlers to respond within the 200 ms INP threshold.
Performance Impact
TBT
50–200ms
main thread freed from per-frame layout and paint during load animations
CLS
0.05–0.25
transform does not affect document flow; layout-shifting animations eliminated
Risk: Low
transform and opacity achieve the same visual effects — requires CSS refactoring
How it works
01
Replace position/size animations with transform
Instead of animating
left/top, usetransform: translate(x, y). Instead of animatingwidth/height, usetransform: scale(sx, sy).transformdoes not affect document flow — other elements do not reflow around it.02
Replace visibility animations with opacity
Instead of toggling
display: none/blockor animatingvisibility, animateopacityfrom 0 to 1. Combine withpointer-events: nonewhen fully transparent to prevent invisible elements from capturing clicks.03
Use will-change to promote elements ahead of time
Add
will-change: transformto elements about to animate to hint the browser to create a GPU compositing layer in advance. Do not apply it to more than 4–5 elements at once — each layer consumes GPU memory.04
Audit with DevTools Rendering panel
Open Chrome DevTools → More tools → Rendering. Enable ‘Paint flashing’ — green overlays appear on regions being repainted. Non-composited animations show constant green flashing. ‘Layer borders’ shows which elements are on their own compositor layer (outlined in orange).
Limitations
- transform: scale() changes the visual size without changing layout dimensions — surrounding content does not reflow, which is the desired behaviour but may require adjusting surrounding spacing.
- will-change should be applied dynamically (added on hover/focus start, removed on end) for elements that only occasionally animate, to avoid permanent GPU memory usage.
- Some design effects (animating box-shadow, border-radius at large scale) are difficult to achieve without non-composited properties — use filter: drop-shadow as a composited alternative to box-shadow where possible.
Common Mistakes
Adding will-change to every animated element as a blanket optimisation
will-change creates a GPU compositing layer immediately and holds it permanently. Too many layers exhausts GPU memory and can make the page slower. Add will-change only to elements with complex, frequently-triggered animations.
Animating margin or padding to create spacing effects
margin and padding affect document flow — every frame reflows all adjacent elements. Use transform: translate() for motion effects and keep spacing properties static.
Need help applying this to your site?
Targeted fixes, not guesswork. We identify which techniques move the needle for your specific WordPress stack.
Start an Engagement