--- title: "CSS Animation Performance: From Jank to 60fps" description: "Practical tips for smoother CSS animations by understanding the rendering pipeline and avoiding expensive layout/paint work." date: "2026-05-20" tags: ["CSS", "performance", "frontend"] image: "" --- ## 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: ```css /* ✅ Recommended: composite-only */ .element { transform: translateX(100px); opacity: 0.5; } /* ❌ Avoid: triggers layout */ .element { left: 100px; width: 200px; } ``` ## Use will-change (carefully) ```css .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: 1. **F**irst: measure the initial position 2. **L**ast: measure the final position 3. **I**nvert: apply the delta as an inverse transform 4. **P**lay: remove the inverse transform with a transition ```javascript 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.