2.0 KiB
| title | description | date | tags | image | |||
|---|---|---|---|---|---|---|---|
| CSS Animation Performance: From Jank to 60fps | Practical tips for smoother CSS animations by understanding the rendering pipeline and avoiding expensive layout/paint work. | 2026-05-20 |
|
Why animations drop frames
The browser rendering pipeline is roughly: JavaScript → Style → Layout → Paint → Composite.
Most “janky” animations are caused by triggering Layout or Paint too often. At 60fps you only get about 16ms per frame — if layout/paint takes too long, frames get dropped.
Prefer properties that only hit Composite
transform and opacity can be handled on the compositor thread, avoiding layout and paint:
/* ✅ Recommended: composite-only */
.element {
transform: translateX(100px);
opacity: 0.5;
}
/* ❌ Avoid: triggers layout */
.element {
left: 100px;
width: 200px;
}
Use will-change (carefully)
.element {
will-change: transform;
}
This hints the browser that the element is likely to change soon, which can help it prepare a separate layer. Don’t overuse it — extra layers consume GPU memory.
Use the FLIP technique
For animations that can’t be expressed purely as transforms, FLIP is a reliable pattern:
- First: measure the initial position
- Last: measure the final position
- Invert: apply the delta as an inverse transform
- Play: remove the inverse transform with a transition
const first = el.getBoundingClientRect()
// mutate layout...
const last = el.getBoundingClientRect()
const invert = first.top - last.top
el.style.transform = `translateY(${invert}px)`
requestAnimationFrame(() => {
el.style.transition = 'transform 0.3s'
el.style.transform = ''
})
Summary
One guiding principle: aim for composite-only animations whenever possible. Prefer transform over top/left, and use opacity when you can. Add will-change and FLIP when they truly help.