The Tale of Animation Performance

A Developer's Guide to 60 FPS

Animation brings websites to life, but poorly implemented animations can destroy user experience. This guide explores how browsers render animations and reveals the techniques professionals use to achieve silky-smooth 60 FPS performance. Understanding the browser's rendering pipeline is the first step to mastering web animation.

Key topics covered:

  • The browser rendering pipeline explained
  • GPU-accelerated properties: transform and opacity
  • CSS animations vs JavaScript requestAnimationFrame
  • Off-main-thread animation (OMTA)
  • Practical techniques for achieving 60 FPS
  • Common pitfalls and how to avoid them

Whether you're building a simple hover effect or a complex interactive experience, these principles will help you create animations that delight users without compromising performance. For comprehensive web development services, our team specializes in creating performant, accessible web interfaces.

Animation Performance by the Numbers

60FPS

Target frame rate for smooth animation

16.67ms

Budget per frame at 60 FPS

2

GPU-accelerated CSS properties

0

Layout recalculations needed for transform/opacity

The Browser Rendering Pipeline

Before diving into animation techniques, it's essential to understand how browsers transform your code into the pixels users see on screen. The rendering pipeline consists of several distinct phases, and where your animation touches this pipeline determines its performance cost.

According to MDN's animation performance guide, understanding these stages is crucial for writing performant animations.

The Four Stages of Rendering

The browser rendering process follows these key steps:

  1. JavaScript - Execution of scripts that modify the DOM or CSS
  2. Style - Calculation of computed styles for all elements
  3. Layout - Calculation of element positions and sizes (reflow)
  4. Paint - Filling in pixels for visible elements
  5. Composite - Combining layers into the final image

When you animate a CSS property, the browser must run through one or more of these stages. The earlier the stage, the more expensive the operation.

Layout vs Paint vs Composite

Understanding the cost difference between these operations is crucial:

StageDescriptionPerformance Cost
LayoutCalculates position and size of every elementHighest - forces recalculation of entire document
PaintFills in pixels for visible elementsMedium - must redraw affected areas
CompositeCombines layers into final imageLowest - GPU handles this efficiently

Animating properties that trigger layout forces the browser to recalculate element positions, which can cascade through the entire document. This is the most expensive type of animation.

Animating paint-only properties is more efficient but still requires work from the CPU.

Animating composite properties like transform and opacity can run entirely on the GPU, making them the most performant choice for smooth animations.

Layout vs Composite Animation Comparison
1/* ❌ BAD: Triggers layout recalculation */2.element {3 left: 0;4 transition: left 0.3s ease;5}6 7.element:hover {8 left: 100px; /* Forces layout, paint, then composite */9}10 11/* ✅ GOOD: Only triggers composite */12.element {13 transform: translateX(0);14 transition: transform 0.3s ease;15}16 17.element:hover {18 transform: translateX(100px); /* GPU handles this */19}

GPU-Accelerated Properties: Your Best Friends

Now that you understand the rendering pipeline, let's focus on the properties that give you maximum performance: transform and opacity. These are the only CSS properties that can be animated with zero layout or paint impact, making them the foundation of high-performance web animations.

As web.dev's animations guide explains, focusing on transform and opacity is the most effective way to achieve smooth animations.

Why Transform and Opacity Are Special

The browser's compositor can handle transform and opacity changes entirely on the GPU, without involving the main thread. This means:

  • No JavaScript blocking affects the animation
  • Animations continue smoothly even during heavy processing
  • Memory usage increases slightly (for compositor layers), but CPU usage stays low

Transform: The Versatile Performer

The transform property is incredibly versatile, offering multiple transformation functions:

  • translateX(), translateY(), translateZ() - Move elements
  • scaleX(), scaleY(), scaleZ() - Resize elements
  • rotateX(), rotateY(), rotateZ() - Rotate elements
  • skewX(), skewY() - Shear elements

For 2D animations, translateX() and translateY() are your workhorses. They replace left, right, top, and bottom with a fraction of the performance cost.

Opacity: The Lightweight Champion

Opacity is equally performant because it doesn't change the element's geometry at all. A semi-transparent element still takes the same space and has the same layout as a fully opaque one - the browser simply renders fewer pixels.

This makes opacity perfect for:

  • Fade in/out effects
  • Hover state transitions
  • Modal and overlay animations
  • Loading spinners and indicators

Browser Compatibility

Both transform and opacity enjoy universal browser support, including all modern browsers:

  • Chrome 36+
  • Firefox 16+
  • Safari 9+
  • Edge 12+

You can use these properties confidently knowing they'll work across virtually all your users' devices.

For a complete breakdown of which properties trigger layout, paint, or composite, refer to CSS Triggers.

Transform and Opacity Best Practices
1/* Move element without layout changes */2.card:hover {3 transform: translateY(-4px);4 box-shadow: 0 10px 20px rgba(0,0,0,0.15);5}6 7/* Scale without layout recalculation */8.button:active {9 transform: scale(0.95);10}11 12/* Fade effect - most efficient animation */13.modal {14 opacity: 0;15 transform: translateY(20px);16 transition: opacity 0.3s ease, transform 0.3s ease;17}18 19.modal.visible {20 opacity: 1;21 transform: translateY(0);22}23 24/* 3D transform forces GPU acceleration */25.3d-element {26 transform: perspective(1000px) rotateY(45deg);27}

CSS Animations vs JavaScript: Making the Right Choice

Both CSS and JavaScript can drive animations, and each has its strengths. Understanding when to use each approach will help you build both performant and maintainable interfaces. Our web development team regularly applies these principles to create smooth, engaging user experiences.

CSS Animations and Transitions

CSS animations excel for predictable, declarative animations:

Transitions handle simple state changes between two values:

  • Hover effects
  • Focus states
  • Modal open/close
  • Menu open/close

Animations (keyframes) handle complex multi-step sequences:

  • Loading spinners
  • Page load effects
  • Complex UI feedback
  • Repeated animations

The key advantage: CSS animations on transform/opacity run on the compositor thread, meaning they continue smoothly even if JavaScript is blocked or the main thread is busy.

requestAnimationFrame: JavaScript's Animation Solution

For animations requiring dynamic control, JavaScript's requestAnimationFrame is the answer. Unlike setInterval or setTimeout, rAF synchronizes with the browser's refresh rate:

function animate(timestamp) {
 // Calculate progress based on timestamp
 const progress = (timestamp - startTime) / duration;
 
 // Update element position
 element.style.transform = `translateX(${progress * 100}px)`;
 
 // Continue animation
 if (progress < 1) {
 requestAnimationFrame(animate);
 }
}
requestAnimationFrame(animate);

According to MDN's documentation, requestAnimationFrame provides several advantages:

  • Syncs with display refresh (usually 60Hz)
  • Pauses when tab is hidden (battery saving)
  • Provides accurate timestamps
  • Avoids animation drift

When to Use Each Approach

ScenarioRecommended Approach
Hover effectsCSS transitions
Focus statesCSS transitions
Page load animationsCSS keyframes
Loading indicatorsCSS keyframes
Scroll-linkedJavaScript rAF
Physics-based motionJavaScript rAF
Interactive sequencesJavaScript rAF
Complex timelinesGSAP or similar

The Hybrid Approach

Many production sites use both approaches together. CSS handles simple UI feedback while JavaScript manages complex, interactive animations. This gives you the performance of CSS with the flexibility of JavaScript. When building interactive web applications, this hybrid approach often provides the best user experience.

For complex animation requirements, consider using animation libraries like GSAP which are optimized for performance and provide sophisticated animation controls.

Off-Main-Thread Animation (OMTA)

One of the most powerful concepts in web animation is Off-Main-Thread Animation (OMTA). When enabled, browsers can animate transform and opacity properties completely independently of JavaScript execution.

As documented in MDN's performance guide, OMTA represents a significant advancement in browser animation capabilities.

How OMTA Works

Normally, even CSS animations require some coordination with the browser's main thread. OMTA takes this further by:

  1. Promoting animated elements to their own compositor layers
  2. Running all transform/opacity changes on the GPU
  3. Allowing animations to continue even during JavaScript execution
  4. Eliminating main thread blocking as a concern for simple animations

Enabling OMTA

In Firefox, you can enable OMTA testing via about:config:

  1. Navigate to about:config
  2. Search for layers.offmainthreadcomposition.async-animations
  3. Set to true

After enabling OMTA, you'll often see significantly higher FPS for CSS animations, even when the main thread is busy with JavaScript.

The Practical Impact

Consider a scenario where JavaScript is processing data while an animation should play:

  • Without OMTA: Animation may stutter as JavaScript competes for resources
  • With OMTA: Animation plays at full 60 FPS while JavaScript runs in parallel

This is particularly valuable for:

  • Loading screens with animations
  • Background UI updates during data processing
  • Complex applications with both interactive and decorative animations

When building modern web applications that handle complex operations, OMTA ensures your animations remain smooth regardless of background processing demands.

Achieving 60 FPS: Practical Techniques

Now for the actionable part - specific techniques you can apply today to make your animations faster.

The 60 FPS Budget

At 60 frames per second, you have approximately 16.67 milliseconds to render each frame. Everything - JavaScript, style calculation, layout, paint, and composite - must fit within this window. Dropping frames creates jank that users immediately notice.

Technique 1: Choose the Right Property

Always prefer these properties for animation:

/* ✅ DO: Animate only transform and opacity */
.animated {
 transform: translateX(100px);
 opacity: 0.5;
 transition: transform 0.3s, opacity 0.3s;
}

Avoid these properties:

  • width, height - Use transform: scale() instead
  • margin, padding - Use transform or padding-box tricks
  • top, left, right, bottom - Use transform: translate() instead
  • font-size - Use transform: scale() or SVG
  • border-width - Use pseudo-elements with scale

Technique 2: Use will-change Wisely

The will-change property hints to the browser that an element will be animated:

.card {
 /* Prepare for upcoming animation */
 will-change: transform;
}

.card:hover {
 transform: translateY(-4px);
}

.card:not(:hover) {
 /* Remove hint when animation ends */
 will-change: auto;
}

Warning: Over-using will-change creates too many compositor layers, increasing memory usage. Use it sparingly and remove it when animations complete.

Technique 3: Reduce Paint Areas

Paint operations cover the area that changes. To minimize paint cost:

  • Isolate animated elements (don't animate large containers)
  • Use will-change to create compositing layers
  • Consider contain: paint for complex components

Technique 4: Optimize Your Selectors

Simple selectors calculate faster:

/* ✅ FAST: Simple class selector */
.animated { }

/* 🐢 SLOW: Complex descendant selectors */
body main section div.article .animated { }

Technique 5: Respect User Preferences

Many users prefer reduced motion:

@media (prefers-reduced-motion: reduce) {
 .animated {
 animation: none !important;
 transition: none !important;
 }
}

This is both a performance optimization and an accessibility requirement. Building accessible web experiences means respecting these user preferences across all your animations.

For more on building performant web interfaces, explore our guides on frontend optimization techniques.

Animation Property Performance Comparison
PropertyTriggersPerformanceRecommendation
transform: translateX()CompositeExcellent✅ Use freely
transform: scale()CompositeExcellent✅ Use freely
opacityCompositeExcellent✅ Use freely
filter: blur()PaintGood⚠️ Use sparingly
box-shadowPaintGood⚠️ Use sparingly
background-colorPaintGood⚠️ Use sparingly
widthLayout + PaintPoor❌ Avoid
heightLayout + PaintPoor❌ Avoid
top, left, right, bottomLayout + PaintPoor❌ Avoid
font-sizeLayout + PaintPoor❌ Avoid

Common Animation Mistakes and How to Fix Them

Even experienced developers make these mistakes. Here's how to identify and fix them.

Mistake 1: Animating Layout Properties

The Problem:

/* ❌ This triggers layout on every frame */
.modal {
 width: 300px;
 transition: width 0.3s;
}
.modal.expanded {
 width: 600px;
}

The Fix:

/* ✅ Use transform and opacity instead */
.modal {
 transform: scaleX(1);
 opacity: 1;
 transition: transform 0.3s, opacity 0.3s;
}
.modal.expanded {
 transform: scaleX(2);
 /* Or use width on an inner element instead */
}

Mistake 2: Overly Complex Keyframes

The Problem: Too many keyframes create unnecessary work.

The Fix: Simplify and use easing functions:

/* ❌ Too many keyframes */
@keyframes complex {
 0% { transform: translateX(0); }
 10% { transform: translateX(10px); }
 20% { transform: translateX(20px); }
 /* ... continues for 20 steps ... */
}

/* ✅ Simpler with easing */
@keyframes simple {
 0% { transform: translateX(0); }
 100% { transform: translateX(200px); }
}
.element {
 animation: simple 2s ease-in-out infinite;
}

Mistake 3: Animating Too Many Elements

The Problem: Animating 50+ items simultaneously overwhelms the compositor.

The Fix: Stagger animations:

/* ❌ All animate at once */
.item { animation: fadeIn 0.3s; }

/* ✅ Staggered with delay */
.item:nth-child(1) { animation-delay: 0ms; }
.item:nth-child(2) { animation-delay: 50ms; }
.item:nth-child(3) { animation-delay: 100ms; }
/* And so on... */

Mistake 4: Ignoring Accessibility

The Problem: Animations can cause discomfort for users with vestibular disorders.

The Fix: Always provide a reduced-motion experience:

@media (prefers-reduced-motion: reduce) {
 .animated-element {
 animation: none !important;
 transition: none !important;
 }
}

Building accessible web applications means considering users who experience motion sensitivity. This simple media query ensures your animations don't cause discomfort.

Performance Testing and Debugging

Measuring animation performance is essential for optimization. Here's how to use browser tools effectively.

Chrome DevTools Performance Tab

  1. Open DevTools (F12) and go to Performance
  2. Click Record
  3. Perform the animation
  4. Stop recording and analyze:
  • Look for long frames (>16ms)
  • Check for layout thrashing patterns
  • Identify JavaScript taking too long

Firefox FPS Meter

  1. Go to about:config
  2. Enable layers.acceleration.draw-fps
  3. You'll see FPS counter in the top-left corner
  4. Watch for drops during animation

Rendering Tab Tools

In Chrome DevTools Rendering tab, enable:

  • Paint Flashing - Highlights areas being repainted
  • Layer Borders - Shows compositor layer boundaries
  • Frame Rendering Stats - Shows FPS and frame time

Measuring Composite-Only Animations

To verify you're animating only composite properties:

  1. Open DevTools Performance tab
  2. Record animation
  3. Look for "Layout" and "Paint" phases
  4. Ideally, these should be absent during animation frames

Testing on Real Devices

DevTools device simulation is useful but not perfect. Always test on:

  • Target mobile devices (different processors, screen sizes)
  • Lower-end devices that your users might have
  • Battery-saving modes that may affect animation smoothness

When testing web applications for production, real-device testing across multiple form factors ensures consistent performance for all users.

Animation Performance Checklist

Use this checklist before deploying any animation

Use transform and opacity only

These are the only GPU-accelerated properties

Choose CSS for simple animations

CSS animations run on compositor thread when possible

Use requestAnimationFrame for complex cases

Synchronizes with browser refresh rate

Test on target devices

DevTools simulation isn't the same as real devices

Use will-change sparingly

Too many layers = memory problems

Respect prefers-reduced-motion

Accessibility requirement for users with vestibular disorders

Measure with DevTools

FPS counters and performance profiling help identify issues

Avoid layout-triggering properties

width, height, top, left are expensive to animate

Frequently Asked Questions

Can I animate any CSS property?

Technically yes, but not all are performant. Only `transform` and `opacity` run entirely on the GPU. Animating properties like `width`, `height`, `margin`, or `font-size` triggers expensive layout recalculations that can cause janky animations.

Why is my CSS animation still laggy?

Check if you're accidentally animating a layout-triggering property. Also verify: you're not animating too many elements simultaneously, the browser isn't overwhelmed by paint operations, and no JavaScript is blocking the main thread during animation.

Should I use CSS or JavaScript animations?

Use CSS for simple, predictable animations (hover effects, loading states, transitions). Use JavaScript with requestAnimationFrame for complex, interactive animations (scroll-linked effects, physics, conditional sequences). For the best of both, use CSS for declarative animations and JavaScript for control.

What is the difference between transitions and animations?

Transitions animate between two states (A to B) and trigger when a property changes. Animations (keyframes) define multiple steps and can run automatically, loop, and have complex timing functions without property changes.

How do I test animation performance?

Use browser DevTools: Chrome's Performance tab and Rendering tool, or Firefox's layers.acceleration.draw-fps preference. Look for FPS drops, long frames, and unnecessary layout/paint operations during animation frames.

What is will-change and should I use it?

will-change hints to the browser that an element will be animated, allowing it to prepare compositor layers. Use it only when needed (not by default) and consider removing it after animations complete. Over-use increases memory usage unnecessarily.

Build High-Performance Web Experiences

Ready to create animations that delight users without compromising performance? Our team specializes in building fast, smooth, accessible web interfaces.

Sources

  1. MDN Web Docs - CSS and JavaScript animation performance - Comprehensive comparison of CSS vs JavaScript animations with performance benchmarks
  2. web.dev - How to create high-performance CSS animations - Google-authored guide focusing on transform and opacity properties
  3. Algolia Blog - 60 FPS: Performant web animations for optimal UX - Engineering perspective on frame rate optimization
  4. CSS Triggers - Reference for which CSS properties trigger layout, paint, or composite