Achieving smooth 60fps animations requires understanding browser rendering.
The Rendering Pipeline
- JavaScript - Calculate animations
- Style - Compute styles
- Layout - Calculate positions and sizes
- Paint - Fill in pixels
- Composite - Layer compositing
Compositor-Only Properties
These skip layout and paint:
css/* ✅ Fast - compositor only */ .element { transform: translateX(100px); transform: scale(1.5); transform: rotate(45deg); opacity: 0.5; } /* ❌ Slow - triggers layout */ .element { width: 200px; height: 200px; top: 100px; left: 100px; margin: 20px; padding: 10px; } /* ⚠️ Medium - triggers paint */ .element { background-color: red; border-color: blue; box-shadow: 0 0 10px black; }
will-change Property
Hint to browser for optimization:
css/* Use sparingly! */ .animated-element { will-change: transform, opacity; } /* Remove when not animating */ .animated-element.idle { will-change: auto; }
jsx// In React, apply dynamically function AnimatedCard({ isAnimating }) { return ( <div style={{ willChange: isAnimating ? 'transform' : 'auto', transform: isAnimating ? 'scale(1.1)' : 'scale(1)', }} > Content </div> ); }
GPU Acceleration
css/* Force GPU layer */ .accelerated { transform: translateZ(0); /* or */ transform: translate3d(0, 0, 0); }
Avoid Layout Thrashing
jsx// ❌ Bad: Reading and writing alternately items.forEach((item) => { const height = item.offsetHeight; // Read item.style.height = height + 10 + 'px'; // Write }); // ✅ Good: Batch reads, then batch writes const heights = items.map((item) => item.offsetHeight); items.forEach((item, i) => { item.style.height = heights[i] + 10 + 'px'; });
React-Specific Tips
jsx// Use CSS instead of inline styles for static values // ❌ Recreates object every render <div style={{ transform: 'translateX(0)' }} /> // ✅ CSS class <div className="initial-position" /> // ✅ Or memoize const style = useMemo(() => ({ transform: 'translateX(0)' }), []); <div style={style} />
Measuring Performance
js// Check for dropped frames let lastTime = performance.now(); function checkFPS() { const now = performance.now(); const delta = now - lastTime; if (delta > 16.67) { // More than 1 frame at 60fps console.log(`Dropped frame: ${delta.toFixed(2)}ms`); } lastTime = now; requestAnimationFrame(checkFPS); }