All articles
Performance

CSS Animation Performance: What to Animate and What to Avoid

13 January 2025 8 min read Performance

Learn which CSS properties trigger layout, paint or composite operations. Build silky 60fps animations by only animating transform and opacity, with real profiling examples.

Nothing kills the feel of a web app faster than janky animation. A stutter at 24fps where you expect 60fps is immediately noticeable — and almost always avoidable. The secret? Know which CSS properties are cheap and which are expensive.

The browser rendering pipeline

To understand animation performance, you need to understand what the browser does every frame:

  1. Style — determine which CSS rules apply to each element
  2. Layout — calculate size and position of every element
  3. Paint — fill in pixels (colours, shadows, text)
  4. Composite — layer the painted elements together and send to the GPU

Layout is the most expensive step. Paint is medium. Compositing is nearly free because it happens on the GPU. So the goal is simple: animate only properties that trigger compositing, not layout or paint.

The only two properties you should animate

In almost every case, you should animate only two CSS properties:

  • transform (translate, scale, rotate, skew)
  • opacity

These properties are handled entirely by the compositor — the GPU does the work, not the main thread. This means they run at 60fps even when JavaScript is doing heavy work.

/* ✓ Good — compositor only */
.card { transition: transform 0.2s ease, opacity 0.2s ease; }
.card:hover { transform: translateY(-4px); opacity: 0.9; }

/* ✗ Bad — triggers layout */
.card:hover { margin-top: -4px; }

/* ✗ Bad — triggers paint */
.card:hover { background-color: #1a1a1a; }
will-change — use it carefully

will-change hints to the browser that an element will be animated, letting it promote it to its own compositor layer in advance:

/* Promote before animation starts */
.modal { will-change: transform, opacity; }

/* Remove after animation ends */
modal.addEventListener('transitionend', () => {
  modal.style.willChange = 'auto';
});

Warning: Don't slap will-change: transform on everything. Each promoted layer consumes GPU memory. On low-end devices, over-promotion causes worse performance than no promotion at all.

Diagnosing jank with DevTools

Open Chrome DevTools → Performance tab → record while animating. Look for:

  • Long purple bars — Layout thrashing
  • Long green bars — Paint operations
  • Red triangles on frames — Dropped frames

The Rendering tab (via the three-dot menu → More tools) has a "Paint flashing" toggle that highlights elements being repainted in green. If your animated element is flashing green, it's triggering paint — switch to transform.