Lesson 17 / Motion

CSS Animation

Time
55 min
Type
Reading + Interactive
Level
Intermediate
Use
Core

Use `@keyframes` and animation properties to create named motion sequences, loading states, and purposeful visual feedback.

Course Role

Core

Part of the main course path. Prioritize this before moving to optional polish or deeper references.

Teacher Notes / In-Class Use

Demo Live

  • Walk through the interactive demo before students start changing their own project files.
  • Connect the demo back to the first goal: Explain when to use `animation` instead of `transition`

Try In Class

  • Build a one-time fade-in animation with `@keyframes`.
  • Have students make one visible change, save, refresh, and explain what changed.

Submit Or Check

  • Ask students to show the work in the browser, not only in the editor.
  • Have students commit their progress with a clear message when the checkpoint is stable.

Watch For

  • Students copying code without checking file paths, spelling, or capitalization.
  • Visual changes that work locally but break when the project is published.

Learning Goals

  • Explain when to use `animation` instead of `transition`
  • Create animations with `@keyframes`, `from`, `to`, and percentage steps
  • Control animation timing, repetition, direction, fill behavior, pause state, and reduced-motion fallbacks

Interactive Demo

How to use this demo.

Use the demo as a small lab. Change one thing, observe the result, then connect it back to your own project.

What To Try

  • Switch between animation names and adjust duration, iteration count, and direction.
  • Replay the animation after changing one setting at a time.

What Changes

  • The target element follows a different keyframe sequence.
  • The code sample shows how animation settings connect to @keyframes.

What To Notice

  • Keyframes define what changes over time.
  • Animation settings control how often, how long, and in what direction the keyframes run.

Apply It

  • Create one small animation that supports meaning, feedback, or attention in your project.

Interactive Demo

Animation Keyframe Lab

Pick an animation, adjust its timing, and restart it. CSS animations use keyframes to define a reusable sequence of visual states.

Animate

fade / 900ms / once / normal

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

.element {
  animation: fade 900ms ease-in-out 1 normal both;
}

What to notice

  • Keyframes define the states of the animation.
  • The animation property applies the keyframes to an element.
  • Iteration and direction change how the sequence repeats.

Try this

  • Compare bounce with pulse.
  • Set iteration to infinite, then change direction to alternate.
  • Copy one @keyframes block into your own stylesheet.

This demo uses extra JavaScript for teaching. The code sample shows the pattern to practice. View full demo source.

Introduction

CSS animations run named @keyframes over time. A keyframe animation can have multiple steps, repeat, pause, reverse, and run without waiting for a hover or class change.

Use animations for loaders, progress indicators, attention cues, entrance effects, and motion sequences that need more than a simple state change.

Transition vs Animation

A transition animates a change between two CSS states. An animation runs a named @keyframes sequence.

NeedUseWhy
Button changes on hovertransitionThe element moves between two states.
Spinner loops forever while loadinganimationThe motion repeats without a state change.
Progress bar fills onceanimationThe sequence has a clear start and end.
Card lifts when hoveredtransitionThe hover state controls the change.
Attention pulse runs three timesanimationThe motion needs timing and repetition control.

Animation Properties

Animations have several properties that control what runs, how long it runs, how it moves, and what happens before and after it runs.

PropertyControlsExample
animation-nameWhich @keyframes sequence runsfadeIn
animation-durationHow long one cycle takes600ms
animation-timing-functionHow speed changes during the cycleease-out
animation-delayHow long to wait before starting200ms
animation-iteration-countHow many times it runs1, 3, infinite
animation-directionWhether cycles run forward, reverse, or alternatealternate
animation-fill-modeWhether styles apply before or after the animationboth
animation-play-stateWhether the animation is running or pausedpaused

Creating Animations with Keyframes

The @keyframes rule defines the animation sequence. Use from and to for simple start/end animations, or percentages for multi-step animations.

from is the same as 0%. to is the same as 100%.

@keyframes fadeIn {
  from {
    opacity: 0;
  }

  to {
    opacity: 1;
  }
}

Applying an Animation

Use the animation shorthand to attach a keyframe sequence to an element.

A common shorthand pattern is animation: name duration timing-function delay iteration-count direction fill-mode.

.fade-in {
  animation: fadeIn 600ms ease-out both;
}

Individual Animation Properties

Writing the properties separately can make an animation easier to read while you are learning.

.fade-in {
  animation-name: fadeIn;
  animation-duration: 600ms;
  animation-timing-function: ease-out;
  animation-fill-mode: both;
}

Animation Fill Mode

animation-fill-mode controls whether the animated styles apply before the animation starts, after it ends, both, or neither.

both is useful for entrance animations because the element can begin in the first keyframe state and stay in the final keyframe state.

ValueMeaning
noneThe animation styles do not apply before or after the animation.
forwardsThe element keeps the final keyframe styles after the animation ends.
backwardsThe element uses the first keyframe styles during the delay.
bothCombines forwards and backwards.

Repeating Animations

animation-iteration-count controls how many times an animation runs. Use infinite only when repeated motion has a clear purpose, such as a loading spinner.

.spinner {
  animation: spin 800ms linear infinite;
}

Pausing Animations

animation-play-state can pause and resume an animation. This is useful for interactive demos, media-like controls, or pausing decorative motion on hover.

.marquee {
  animation: slide 12s linear infinite;
}

.marquee:hover {
  animation-play-state: paused;
}

Good Properties to Animate

Like transitions, animations are usually smoother when they use properties that do not force layout recalculation.

Property TypeExamplesGuidance
Bestopacity, transformUse these for most movement and fading.
Use carefullybox-shadow, filterCan be useful, but may be heavier on large elements.
Avoid for motionwidth, height, top, left, marginThese can trigger layout recalculation and feel less smooth.

Example: One-time Fade In

A fade-in animation is useful when content enters the page or appears after loading.

@keyframes fadeIn {
  from {
    opacity: 0;
    transform: translateY(0.5rem);
  }

  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.fade-in {
  animation: fadeIn 600ms ease-out both;
}

Example: Pulse Attention Cue

A pulse can draw attention, but it should stop after a few cycles unless it communicates an ongoing status.

@keyframes pulse {
  0%, 100% {
    transform: scale(1);
  }

  50% {
    transform: scale(1.06);
  }
}

.notice {
  animation: pulse 700ms ease-in-out 3;
}

Example: Loading Spinner

A spinner is one of the clearest uses for an infinite animation because it communicates ongoing loading.

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

.spinner {
  inline-size: 2rem;
  block-size: 2rem;
  border: 4px solid #ddd;
  border-top-color: #2f9e44;
  border-radius: 50%;
  animation: spin 800ms linear infinite;
}

Example: Progress Bar

A progress bar can animate with transform: scaleX() from left to right. This avoids animating width.

@keyframes progress {
  from {
    transform: scaleX(0);
  }

  to {
    transform: scaleX(1);
  }
}

.progress-bar {
  transform-origin: left;
  animation: progress 2s ease forwards;
}

Example: Skeleton Shimmer

A skeleton shimmer can suggest loading content, but avoid making it too bright, fast, or distracting.

@keyframes shimmer {
  from {
    transform: translateX(-100%);
  }

  to {
    transform: translateX(100%);
  }
}

.skeleton::after {
  animation: shimmer 1.2s linear infinite;
}

Triggering Animations with JavaScript

JavaScript can trigger CSS animations by adding or removing a class. This keeps the animation definition in CSS and the interaction logic in JavaScript.

.animated-box.is-animating {
  animation: pulse 700ms ease-in-out;
}

const button = document.querySelector('.animation-button');
const box = document.querySelector('.animated-box');

button.addEventListener('click', () => {
  box.classList.remove('is-animating');

  requestAnimationFrame(() => {
    box.classList.add('is-animating');
  });
});

Accessibility and Motion Safety

  • Avoid constant motion near long reading content.
  • Do not make important information depend only on animation.
  • Avoid flashing, flickering, or rapid high-contrast changes.
  • Use prefers-reduced-motion to reduce or remove non-essential animations.
  • Make infinite animations purposeful, such as showing loading or ongoing activity.
@media (prefers-reduced-motion: reduce) {
  .fade-in,
  .notice,
  .spinner,
  .skeleton::after {
    animation: none;
  }
}

Common Mistakes

MistakeWhy It HurtsFix
Forgetting matching @keyframesThe animation name has nothing to runMake sure animation-name matches the @keyframes name.
Using infinite for decorative motionConstant motion can distract or bother usersLimit cycles or stop the animation when it has done its job.
Animating layout propertiesCan cause jank and layout recalculationPrefer transform and opacity.
Forgetting animation-fill-modeThe element may snap back after the animationUse forwards or both when the final state should remain.
Creating motion with no purposeAnimation can make the interface feel noisyUse motion to give feedback, show status, or guide attention.
Skipping reduced motionSome users are sensitive to motionAdd a prefers-reduced-motion fallback.

Checkpoint

Before moving on, make sure these feel true.

  • I can explain the main concept in my own words.
  • I can apply this lesson to my current project.
  • I can verify the result in the browser.
  • I can commit the change with a clear message.

Project Connection

This lesson supports current class projects.

Practice

  • Build a one-time fade-in animation with @keyframes.
  • Create a looping spinner with animation-iteration-count: infinite.
  • Build a progress bar using transform: scaleX() instead of width.
  • Pause an animation with animation-play-state.
  • Trigger an animation by toggling an .is-animating class.
  • Add a prefers-reduced-motion fallback.
  • Explain when you would use animation instead of transition.