Lesson 17 / Motion
CSS Animation
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.
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.
| Need | Use | Why |
|---|---|---|
| Button changes on hover | transition | The element moves between two states. |
| Spinner loops forever while loading | animation | The motion repeats without a state change. |
| Progress bar fills once | animation | The sequence has a clear start and end. |
| Card lifts when hovered | transition | The hover state controls the change. |
| Attention pulse runs three times | animation | The 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.
| Property | Controls | Example |
|---|---|---|
animation-name | Which @keyframes sequence runs | fadeIn |
animation-duration | How long one cycle takes | 600ms |
animation-timing-function | How speed changes during the cycle | ease-out |
animation-delay | How long to wait before starting | 200ms |
animation-iteration-count | How many times it runs | 1, 3, infinite |
animation-direction | Whether cycles run forward, reverse, or alternate | alternate |
animation-fill-mode | Whether styles apply before or after the animation | both |
animation-play-state | Whether the animation is running or paused | paused |
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.
| Value | Meaning |
|---|---|
none | The animation styles do not apply before or after the animation. |
forwards | The element keeps the final keyframe styles after the animation ends. |
backwards | The element uses the first keyframe styles during the delay. |
both | Combines 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 Type | Examples | Guidance |
|---|---|---|
| Best | opacity, transform | Use these for most movement and fading. |
| Use carefully | box-shadow, filter | Can be useful, but may be heavier on large elements. |
| Avoid for motion | width, height, top, left, margin | These 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-motionto 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
| Mistake | Why It Hurts | Fix |
|---|---|---|
Forgetting matching @keyframes | The animation name has nothing to run | Make sure animation-name matches the @keyframes name. |
Using infinite for decorative motion | Constant motion can distract or bother users | Limit cycles or stop the animation when it has done its job. |
| Animating layout properties | Can cause jank and layout recalculation | Prefer transform and opacity. |
Forgetting animation-fill-mode | The element may snap back after the animation | Use forwards or both when the final state should remain. |
| Creating motion with no purpose | Animation can make the interface feel noisy | Use motion to give feedback, show status, or guide attention. |
| Skipping reduced motion | Some users are sensitive to motion | Add 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 ofwidth. - Pause an animation with
animation-play-state. - Trigger an animation by toggling an
.is-animatingclass. - Add a
prefers-reduced-motionfallback. - Explain when you would use
animationinstead oftransition.