cssanimationkeyframetransitionweb designperformance

CSS Animations: @keyframes, Timing Functions, and Performance

How to create smooth CSS animations using @keyframes, animation properties, and timing functions — with guidance on performance and accessibility.

6 min read

Related Tool

CSS Animation Generator

Open tool

CSS Transitions vs CSS Animations

CSS has two systems for movement: transitions and animations. Transitions respond to state changes (hover, focus, class toggle) — they smoothly interpolate a property from its current value to a new one when triggered. Animations use @keyframes to define a sequence of states that play automatically, loop, or reverse without needing a state change trigger.

Use transitions for simple interactive feedback. Use animations for continuous motion, multi-step sequences, or effects that play on load.

@keyframes Syntax

@keyframes slide-in {
  from {
    transform: translateX(-100%);
    opacity: 0;
  }
  to {
    transform: translateX(0);
    opacity: 1;
  }
}

You can use from/to for two-step animations or percentage stops for more complex sequences:

@keyframes bounce {
  0%   { transform: translateY(0); }
  40%  { transform: translateY(-30px); }
  60%  { transform: translateY(-15px); }
  80%  { transform: translateY(-5px); }
  100% { transform: translateY(0); }
}

Applying an Animation

.element {
  animation-name: slide-in;
  animation-duration: 0.4s;
  animation-timing-function: ease-out;
  animation-delay: 0.1s;
  animation-iteration-count: 1;
  animation-fill-mode: both;
}

Or using the shorthand: animation: slide-in 0.4s ease-out 0.1s 1 both;

Timing Functions

The timing function controls how the animation progresses over its duration:

  • ease (default): slow start, fast middle, slow end
  • ease-in: starts slow, ends fast — good for elements leaving the screen
  • ease-out: starts fast, ends slow — good for elements entering the screen
  • ease-in-out: slow at both ends
  • linear: constant speed — good for loops like spinners
  • cubic-bezier(x1, y1, x2, y2): fully custom curve

spring-like physics can be approximated with a cubic-bezier that overshoots: cubic-bezier(0.34, 1.56, 0.64, 1) creates a slight bounce at the end of an entrance animation.

fill-mode

animation-fill-mode determines what styles apply outside the animation's active period:

  • none: element returns to its original style before and after
  • forwards: element keeps the styles of the last keyframe after the animation ends
  • backwards: element has the styles of the first keyframe during the delay period
  • both: combines forwards and backwards

Performance: The Compositor Thread

CSS animations perform best when they only animate properties the browser can handle on the compositor thread — without involving layout or paint. These are: transform (translate, rotate, scale) and opacity. Animating these properties doesn't trigger a layout recalculation, so they run smoothly even under CPU load.

Animating width, height, top, left, or background-color triggers layout or paint, which is much more expensive and causes jank on complex pages.

For any animation involving movement or size change, use transform: translate() or transform: scale() rather than changing positional or dimensional properties directly.

Adding will-change

will-change: transform hints to the browser that an element will be animated, promoting it to its own GPU layer in advance. Use it sparingly — on the specific elements that animate, not globally. Overusing will-change consumes extra GPU memory.

Accessibility: prefers-reduced-motion

Some users experience motion sickness or seizures from animation. Respect their preferences:

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

This media query detects when the user has enabled "Reduce motion" in their OS accessibility settings and disables animations accordingly.