Codeskill

Learn to code, step by step

CSS animations performance and compositing

Next: CSS animation performance. Which properties are cheap to animate, how compositing works, and how to avoid jank on mid-range phones. Pretty motion is useless if it drops frames.

The rendering pipeline in one paragraph

The browser lays out the page (layout), paints pixels (paint), and may promote layers to the GPU (composite). Animating width, height, top, or margin often triggers layout every frame. Animating opacity and transform usually composes on the GPU without relayout. That is the main rule.

Compositor-friendly properties

.drawer {
  transform: translateX(100%);
  transition: transform 0.3s ease;
}

.drawer.is-open {
  transform: translateX(0);
}

.modal-backdrop {
  opacity: 0;
  transition: opacity 0.2s ease;
}

.modal-backdrop.is-visible {
  opacity: 1;
}

Slide with transform: translate, fade with opacity. Avoid animating left / right for the same effect.

Layout-triggering properties to treat carefully

  • width, height, padding, margin, border-width
  • top, left, right, bottom on non-composited elements
  • font-size, line-height on large text blocks
  • box-shadow and filter – paint-heavy; fine for short hovers, risky for long loops

Sometimes you need layout animation – accordion height, for example. Prefer grid-template-rows: 0fr to 1fr tricks or allow a instant snap under reduced motion rather than animating height on a complex subtree.

will-change – use sparingly

.carousel-track {
  will-change: transform;
}

.carousel-track.is-idle {
  will-change: auto;
}

will-change hints the browser to promote a layer early. Overuse wastes memory – each layer holds bitmaps. Apply before animation starts, remove after. Do not sprinkle it on fifty elements ‘just in case’.

contain and content-visibility

Isolation helps the browser limit work:

.feed-item {
  contain: layout style paint;
}

.long-article-list > article {
  content-visibility: auto;
  contain-intrinsic-size: auto 400px;
}

contain tells the engine that changes inside the box rarely affect outside layout. content-visibility: auto skips rendering off-screen sections until needed. Both reduce main-thread work on long pages.

Debugging jank in DevTools

  • Performance panel – record while animating; look for long layout or paint bars.
  • Rendering tab – enable ‘Paint flashing’ and ‘Layer borders’ to see repaints.
  • Animations tab – inspect running CSS animations and their timing.

If frames exceed 16ms (60fps) or 8ms (120fps displays), simplify the effect or reduce the animated subtree.

prefers-reduced-motion again

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

Ship this pattern at the base layer. Individual components can still opt into instant state changes without motion.

The next tutorial covers anchor positioning and popover styling – tooltips, menus, and dialogs positioned relative to a trigger without JavaScript measurement loops.

PreviousView transitions and page transition UX