CSS Animations
CSS animations make elements move, fade, grow, spin, or change over time — automatically, without user interaction needed. Unlike transitions that animate between two states, animations can have multiple steps, loop indefinitely, and run the moment the page loads.
Animations vs Transitions
DIAGRAM — Transition vs Animation:
Transition:
Needs a trigger (hover, focus, class change)
Animates between two states only
Runs once per trigger
Animation:
Can run automatically on page load
Can have any number of steps
Can loop, reverse, pause, and fill
Two Steps to Create an Animation
Every CSS animation requires two things working together:
- A
@keyframesblock — defines what changes and when - An
animationproperty on the element — tells the element which keyframes to use and how
Step 1 — Define @keyframes
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
from is shorthand for 0% (start). to is shorthand for 100% (end). You can also use percentages for more steps.
@keyframes bounce {
0% { transform: translateY(0); }
30% { transform: translateY(-30px); }
50% { transform: translateY(0); }
70% { transform: translateY(-15px); }
100% { transform: translateY(0); }
}
DIAGRAM — Keyframe timeline:
Time: 0% 30% 50% 70% 100%
↓ ↓ ↓ ↓ ↓
Y pos: 0 -30px 0 -15px 0
Browser fills in all the frames between these stops automatically.
Step 2 — Apply the animation Property
/* Shorthand: name | duration | timing | delay | iterations | direction | fill-mode */
.hero-text {
animation: fadeIn 0.6s ease-out 0s 1 normal forwards;
}
/* Most common — just name and duration */
.card {
animation: fadeIn 0.5s ease-out;
}
animation Sub-Properties
| Property | What It Does | Common Values |
|---|---|---|
| animation-name | Which @keyframes to use | fadeIn, bounce, spin |
| animation-duration | How long one cycle takes | 0.3s, 1s, 500ms |
| animation-timing-function | Speed curve | ease, linear, ease-out |
| animation-delay | Wait before starting | 0s, 0.2s, -0.5s |
| animation-iteration-count | How many times to play | 1, 3, infinite |
| animation-direction | Forward, backward, or alternate | normal, reverse, alternate |
| animation-fill-mode | State before/after animation | none, forwards, backwards, both |
| animation-play-state | Running or paused | running, paused |
animation-iteration-count — Looping
/* Play once */
animation: spin 1s linear 1;
/* Play 3 times */
animation: pulse 0.5s ease 3;
/* Loop forever */
animation: spin 2s linear infinite;
animation-direction — Reversing
DIAGRAM — animation-direction options:
normal: A → B A → B A → B (always forward)
reverse: B → A B → A B → A (always backward)
alternate: A → B B → A A → B (ping-pong)
alternate-reverse: B → A A → B B → A (ping-pong, starts backward)
@keyframes breathe {
from { transform: scale(1); }
to { transform: scale(1.1); }
}
.pulse-icon {
animation: breathe 1.5s ease-in-out infinite alternate;
/* Grows and shrinks smoothly, forever */
}
animation-fill-mode — State Before and After
Fill mode controls what styles the element has before the animation starts and after it ends.
DIAGRAM — animation-fill-mode:
Element starts at opacity: 1 (no fill)
@keyframes: from { opacity: 0 } to { opacity: 1 }
none (default):
[opacity: 1] → [animation: 0→1] → [opacity: 1 again]
Element flickers back to original after animation
forwards:
[opacity: 1] → [animation: 0→1] → [stays at opacity: 1]
Holds the final keyframe state ← most commonly needed
backwards:
[opacity: 0 during delay] → [animation: 0→1] → [back to original]
Applies FROM state during delay period
both:
Combines forwards + backwards behavior
/* Fade in and stay visible — use forwards */
.popup {
opacity: 0;
animation: fadeIn 0.5s ease-out forwards;
}
animation-delay — Start Later
/* Start after 1 second */
.card { animation: slideIn 0.5s 1s ease forwards; }
/* Negative delay — start mid-animation */
.spinner { animation: spin 2s linear -0.5s infinite; }
/* Starting at -0.5s means it begins halfway through its first cycle */
Staggered Animations with Delay
.item { opacity: 0; animation: fadeIn 0.4s ease forwards; }
.item:nth-child(1) { animation-delay: 0s; }
.item:nth-child(2) { animation-delay: 0.1s; }
.item:nth-child(3) { animation-delay: 0.2s; }
.item:nth-child(4) { animation-delay: 0.3s; }
DIAGRAM — Staggered card entrance:
t=0.0s: [Card 1 fades in]
t=0.1s: [Card 2 fades in]
t=0.2s: [Card 3 fades in]
t=0.3s: [Card 4 fades in]
Creates a cascading wave effect.
animation-play-state — Pause and Resume
.spinner {
animation: spin 1s linear infinite;
}
.spinner:hover {
animation-play-state: paused; /* pauses on hover */
}
Multiple Animations on One Element
Separate multiple animations with commas.
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
@keyframes slideUp { from { transform: translateY(20px); } to { transform: translateY(0); } }
.hero {
animation:
fadeIn 0.5s ease forwards,
slideUp 0.5s ease forwards;
}
Common Animation Examples
Loading Spinner
@keyframes spin {
to { transform: rotate(360deg); }
}
.loader {
width: 40px;
height: 40px;
border: 4px solid #f0f0f0;
border-top-color: #3498db;
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
Pulsing Dot
@keyframes pulse {
0%, 100% { transform: scale(1); opacity: 1; }
50% { transform: scale(1.4); opacity: 0.6; }
}
.dot {
width: 12px;
height: 12px;
border-radius: 50%;
background: green;
animation: pulse 1.5s ease-in-out infinite;
}
Typing Cursor Blink
@keyframes blink {
0%, 100% { opacity: 1; }
50% { opacity: 0; }
}
.cursor::after {
content: "|";
animation: blink 1s step-end infinite;
}
Shake / Error Effect
@keyframes shake {
0%, 100% { transform: translateX(0); }
20% { transform: translateX(-8px); }
40% { transform: translateX(8px); }
60% { transform: translateX(-6px); }
80% { transform: translateX(6px); }
}
.error {
animation: shake 0.5s ease;
}
Respecting Reduced Motion
@media (prefers-reduced-motion: reduce) {
* {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
}
}
Always include this. Some users experience dizziness or nausea from motion-heavy animations. The OS reduced-motion setting is their signal to turn it off.
Performance — GPU-Safe Properties
DIAGRAM — Which properties animate smoothly:
✅ Animate these freely (GPU-accelerated):
transform (translate, scale, rotate)
opacity
⚠ Use with caution (triggers layout recalc each frame):
width, height, top, left, margin, padding, font-size
Always animate transform and opacity first. If you need to move an element, use transform: translateX(), not left:.
CSS animations handle everything from loading spinners to page entrance effects to complex multi-step sequences. Start with a two-keyframe from/to animation, get comfortable with animation-fill-mode: forwards and infinite loops, and you will cover every common animation pattern on the web.
