ToolHub
查看所有文章

CSS Animation Guide: Transitions, Keyframes, and Performance

CSS animations bring interfaces to life, providing visual feedback, guiding user attention, and creating memorable experiences. When done well, animations feel natural and intuitive. When done poorly, they cause jank, distract users, and even trigger physical discomfort. This guide covers everything from the fundamentals of transitions and keyframes to advanced performance optimization and accessibility, giving you the knowledge to create animations that are smooth, purposeful, and inclusive.

CSS Transitions vs Animations

CSS provides two distinct mechanisms for creating motion: transitions and animations. Understanding when to use each is the foundation of effective CSS animation work.

CSS Transitions

Transitions animate the change between two states of a CSS property. You define which properties to animate, how long the animation takes, and what easing curve to use. The browser handles the interpolation between the start and end values automatically. Transitions are triggered by state changes like hover, focus, class additions, or JavaScript property changes.

.button {
  background: #3b82f6;
  transform: scale(1);
  transition: background 0.3s ease, transform 0.2s ease;
}

.button:hover {
  background: #2563eb;
  transform: scale(1.05);
}

Transitions are ideal for simple, two-state animations. They require minimal code, are easy to understand, and work reliably across browsers. The limitation is that they only animate between two values �?you cannot define intermediate steps.

CSS Animations (Keyframes)

Animations use @keyframes rules to define sequences of style changes across any number of steps. Unlike transitions, animations can run independently of state changes, loop indefinitely, play in reverse, and pause mid-animation. They give you precise control over every frame of the animation.

@keyframes pulse {
  0% {
    transform: scale(1);
    opacity: 1;
  }
  50% {
    transform: scale(1.05);
    opacity: 0.8;
  }
  100% {
    transform: scale(1);
    opacity: 1;
  }
}

.pulse-element {
  animation: pulse 2s ease-in-out infinite;
}

When to Use Each

Scenario Use Transition Use Animation
Hover effects Yes No
Focus states Yes No
Toggle visibility Yes Possible
Multi-step sequences No Yes
Looping animations No Yes
Independent of state No Yes
Entrance animations Possible Yes

Keyframe Syntax Deep Dive

The @keyframes rule is the heart of CSS animations. Understanding its syntax fully unlocks the power of multi-step animation sequences.

Percentage-Based Keyframes

Keyframes are defined using percentage values from 0% to 100%, representing the progression through the animation timeline. You can define as many keyframes as needed, and the browser interpolates between them:

@keyframes bounce {
  0%, 100% {
    transform: translateY(0);
  }
  25% {
    transform: translateY(-20px);
  }
  50% {
    transform: translateY(0);
  }
  75% {
    transform: translateY(-10px);
  }
}

Multiple keyframes can share the same style block by comma-separating the percentage values, as shown with 0%, 100% in the example above. This is useful for animations that return to their starting state.

The Animation Shorthand

The animation shorthand property combines eight sub-properties into a single declaration. The order is: name, duration, timing-function, delay, iteration-count, direction, fill-mode, and play-state.

.element {
  animation: slideIn 0.5s ease-out 0.2s 1 forwards running;
}

/* Equivalent longhand */
.element {
  animation-name: slideIn;
  animation-duration: 0.5s;
  animation-timing-function: ease-out;
  animation-delay: 0.2s;
  animation-iteration-count: 1;
  animation-direction: normal;
  animation-fill-mode: forwards;
  animation-play-state: running;
}

Animation Fill Mode

The animation-fill-mode property controls what styles apply before and after the animation runs. This is one of the most commonly misunderstood animation properties:

Easing Functions

Easing functions (also called timing functions) define the acceleration curve of an animation. They determine whether the animation starts slowly and speeds up, starts quickly and slows down, or follows a more complex pattern. The right easing function makes animations feel natural; the wrong one makes them feel robotic or jarring.

Built-In Easing Functions

Cubic Bezier Curves

The cubic-bezier() function lets you define custom easing curves with precise control. It takes four parameters representing the control points of a cubic Bezier curve: cubic-bezier(x1, y1, x2, y2). The x values represent time (0 to 1) and the y values represent progress (can exceed 0-1 for overshoot).

.element {
  /* Smooth deceleration - great for entrances */
  transition: transform 0.3s cubic-bezier(0.0, 0.0, 0.2, 1.0);

  /* Smooth acceleration - great for exits */
  transition: transform 0.2s cubic-bezier(0.4, 0.0, 1, 1);

  /* Material Design standard easing */
  transition: transform 0.3s cubic-bezier(0.4, 0.0, 0.2, 1);
}

Step Functions

The steps() function creates discrete, frame-by-frame animation instead of smooth interpolation. This is useful for sprite sheet animations or creating a "typing" effect:

@keyframes typing {
  from { width: 0; }
  to { width: 100%; }
}

.typing-effect {
  animation: typing 3s steps(40, end);
  overflow: hidden;
  white-space: nowrap;
}

Performance Optimization

Smooth animations require hitting 60 frames per second (fps), which means each frame must complete in under 16.67 milliseconds. Failing to meet this budget causes visible jank �?stuttering, dropped frames, and a degraded user experience. Understanding the browser rendering pipeline is key to writing performant animations.

The Rendering Pipeline

When a CSS property changes, the browser goes through up to three stages to update the display:

  1. Style: Determine which CSS rules apply to which elements.
  2. Layout: Calculate the size and position of every element. Triggered by changes to width, height, margin, padding, top, left, font-size, and other geometric properties.
  3. Paint: Fill in the pixels for each element. Triggered by changes to color, background, box-shadow, border-radius, and other visual properties.
  4. Composite: Combine the painted layers in the correct order. Only this step runs on the GPU. Triggered by changes to transform and opacity.

Animations that only trigger the composite step are the most performant because they skip layout and paint entirely. This is why transform and opacity are the gold standard for performant CSS animations.

Compositor-Only Properties

Property Triggers Layout? Triggers Paint? GPU Composited?
transform No No Yes
opacity No No Yes
filter No Yes Sometimes
top / left Yes Yes No
width / height Yes Yes No
margin / padding Yes Yes No
background-color No Yes No
box-shadow No Yes No

Using will-change

The will-change property tells the browser which properties are expected to change, allowing it to optimize ahead of time by promoting the element to its own compositor layer. However, it should be used sparingly:

/* Apply will-change just before the animation */
.element:hover {
  will-change: transform;
}

/* Remove it after the animation completes */
.element {
  transition: transform 0.3s ease;
}

Common mistakes with will-change include applying it to too many elements (each compositor layer consumes GPU memory) and leaving it on permanently (wastes resources). A good practice is to add will-change via JavaScript just before the animation starts and remove it when the animation ends.

Transform Instead of Layout Properties

The single most impactful performance optimization is replacing layout-triggering properties with transforms:

Performance Rule: If you can achieve the same visual result with transform or opacity, always prefer those over properties that trigger layout or paint. This single principle eliminates the majority of animation performance problems.

Common Animation Patterns

Certain animation patterns appear repeatedly in web interfaces. Mastering these patterns gives you a toolkit for most common UI animation needs.

1. Fade In

The simplest and most universally applicable entrance animation. Elements transition from transparent to opaque:

@keyframes fadeIn {
  from { opacity: 0; }
  to { opacity: 1; }
}

.fade-in {
  animation: fadeIn 0.4s ease-out forwards;
}

2. Slide Up and Fade

A more dynamic entrance that combines vertical movement with opacity. This creates a sense of the element arriving from below:

@keyframes slideUpFade {
  from {
    opacity: 0;
    transform: translateY(20px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.slide-up {
  animation: slideUpFade 0.5s cubic-bezier(0.4, 0, 0.2, 1) forwards;
}

3. Scale on Hover

A subtle scale increase on hover provides satisfying interactive feedback:

.card {
  transition: transform 0.2s ease, box-shadow 0.2s ease;
}

.card:hover {
  transform: translateY(-4px) scale(1.02);
  box-shadow: 0 12px 24px rgba(0, 0, 0, 0.15);
}

4. Staggered List Animation

When a list of items appears, staggering the animation delay for each item creates a cascading effect that guides the eye:

.list-item {
  opacity: 0;
  transform: translateY(10px);
  animation: slideUpFade 0.3s ease-out forwards;
}

.list-item:nth-child(1) { animation-delay: 0.0s; }
.list-item:nth-child(2) { animation-delay: 0.05s; }
.list-item:nth-child(3) { animation-delay: 0.1s; }
.list-item:nth-child(4) { animation-delay: 0.15s; }
.list-item:nth-child(5) { animation-delay: 0.2s; }

For dynamic lists, use JavaScript to set the animation-delay based on each item's index, or use CSS custom properties: style="animation-delay: calc(var(--i) * 0.05s)".

5. Loading Spinner

A smooth, continuous rotation is the classic loading indicator:

@keyframes spin {
  to { transform: rotate(360deg); }
}

.spinner {
  width: 24px;
  height: 24px;
  border: 3px solid rgba(255, 255, 255, 0.2);
  border-top-color: #3b82f6;
  border-radius: 50%;
  animation: spin 0.8s linear infinite;
}

Animation Accessibility: prefers-reduced-motion

Not all users benefit from animations. For people with vestibular disorders, motion sensitivity, or certain cognitive conditions, animations can cause dizziness, nausea, or seizures. The prefers-reduced-motion media query allows you to respect the user's system-level preference for reduced motion.

Implementing Reduced Motion

The recommended approach is to make animations opt-in for users who have not set a reduced motion preference:

/* Default: no animation for users who prefer reduced motion */
.element {
  opacity: 1;
  transform: none;
}

/* Only animate for users who have no motion preference */
@media (prefers-reduced-motion: no-preference) {
  .element {
    animation: slideUpFade 0.5s ease-out forwards;
  }
}

Alternatively, you can disable specific animations for users who prefer reduced motion:

.element {
  animation: slideUpFade 0.5s ease-out forwards;
}

@media (prefers-reduced-motion: reduce) {
  .element {
    animation: none;
    opacity: 1;
    transform: none;
  }

  /* Keep functional transitions but shorten duration */
  * {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
}

What to Keep and What to Remove

Not all motion should be removed when the user prefers reduced motion. Functional animations that convey meaning or state should be preserved, just simplified:

Animation Libraries

While CSS animations handle most UI needs, JavaScript animation libraries provide advanced capabilities like physics-based motion, scroll-linked animations, and orchestration of complex sequences.

CSS-First Libraries

JavaScript Animation Libraries

When to Use JavaScript vs CSS

Use CSS for simple, declarative animations that respond to state changes. Use JavaScript when you need: physics-based motion (springs, momentum), scroll-linked animations, complex sequencing with precise timing control, dynamic animation parameters calculated at runtime, or animations that need to be paused, reversed, or scrubbed programmatically.

Debugging Animations

Animation bugs can be subtle and difficult to diagnose. The right debugging tools and techniques make the process much more efficient.

Chrome DevTools Animations Panel

Chrome DevTools includes a dedicated Animations panel that provides powerful debugging capabilities:

Performance Profiling

Use the Performance panel in Chrome DevTools to identify animation jank:

Common Debugging Scenarios

Working with CSS? Try our free CSS tools to format, minify, and optimize your stylesheets for production.

CSS Formatter CSS Minifier

Frequently Asked Questions

Should I use CSS transitions or CSS animations?

Use CSS transitions for simple state changes that go from point A to point B, such as hover effects, focus states, and toggling visibility. Use CSS animations (keyframes) for complex, multi-step sequences, looping animations, or animations that need to run independently of state changes. Transitions are simpler to write; animations offer more control.

What CSS properties are safe to animate for performance?

The most performant properties to animate are transform and opacity, because they can be handled entirely by the GPU compositor without triggering layout or paint. Other properties like width, height, margin, and top trigger layout recalculation, which is expensive. Always prefer transform: translateX() over left, and transform: scale() over width/height changes.

How do I make animations accessible for users with motion sensitivity?

Use the prefers-reduced-motion media query to disable or simplify animations for users who have enabled the 'reduce motion' setting in their operating system. Wrap non-essential animations in @media (prefers-reduced-motion: no-preference) and provide instant state changes as the fallback for users who prefer reduced motion.

What is will-change and when should I use it?

will-change is a CSS property that hints to the browser about which properties will change in the future, allowing it to optimize ahead of time. Use it sparingly and only when you observe performance issues. Apply it just before the animation starts and remove it after the animation ends. Overusing will-change wastes GPU memory and can actually hurt performance.

How do I debug CSS animations?

Chrome DevTools provides powerful animation debugging tools. Open the Animations panel (in the More Tools menu) to see all active animations, scrub through timelines, modify durations and delays in real time, and slow animations down to 1/4 speed. You can also use the Performance panel to identify animation jank and layout thrashing.