CSS Animations & Transitions Mastery: From Hover Effects to Keyframe Sequences
Master CSS transitions and animations. Covers timing functions, @keyframes, animation-fill-mode, performance (transform vs layout properties), will-change, practical patterns like loading spinners and staggered entry, and prefers-reduced-motion accessibility.
- 1. CSS Flexbox Mastery: The Complete Visual Guide to Flexible Layouts
- 2. CSS Grid Mastery: From Basics to Advanced Layouts
- 3. CSS Custom Properties (Variables) Mastery: Dynamic Theming & Beyond
- 4. CSS Animations & Transitions Mastery: From Hover Effects to Keyframe Sequences
- 5. CSS Selectors Deep Dive: From Basic to :has() & :is()
- 6. CSS Box Model Mastery: Content, Padding, Border & Margin Explained
- 7. CSS Responsive Design Mastery: Mobile-First, clamp(), & Container Queries
- 8. CSS Specificity & Cascade Mastery: How Browsers Resolve Conflicts
- 9. CSS Modern Layout Techniques: Scroll Snap, Subgrid, Logical Properties & More
- 10. CSS Pseudo-Elements Mastery: ::before, ::after, Counters & Decorative Techniques
CSS animations bring your interfaces to life. A well-placed transition can make a button feel satisfying to click, and a carefully crafted animation can guide a user's attention exactly where you need it. But used badly, they create jank, distraction, and frustration.
In this guide, we'll cover both transitions (simple A-to-B changes) and animations (complex multi-step sequences), with a strong focus on performance — because a beautiful animation that drops frames is worse than no animation at all.
1. Transitions: Smooth State Changes
A CSS transition smoothly animates a property from one value to another when that property changes (on hover, focus, class toggle, etc.).
The Four Ingredients
.button {
transition-property: background-color; /* What to animate */
transition-duration: 0.3s; /* How long */
transition-timing-function: ease; /* Speed curve */
transition-delay: 0s; /* Wait before starting */
}
/* Shorthand */
.button {
transition: background-color 0.3s ease 0s;
}
2. Transition Properties You Can Animate
Not all CSS properties can be transitioned. Here's what works:
| Animatable | Examples |
|---|---|
| ✅ Colors | color, background-color, border-color |
| ✅ Opacity | opacity |
| ✅ Transform | transform (translate, rotate, scale, skew) |
| ✅ Box model | width, height, margin, padding |
| ✅ Shadow | box-shadow, text-shadow |
| ✅ Border radius | border-radius |
| ❌ Display | display: none → can't transition |
| ❌ Font family | Discrete values can't interpolate |
| ⚠️ Height auto | height: 0 → height: auto doesn't work directly |
Pro Tip: When you use
transition: all, every animatable property will transition. This is convenient but can cause unwanted transitions. Be specific when possible:transition: background-color 0.3s, transform 0.3s.
3. Timing Functions Explained
The timing function controls the speed curve of the animation — how fast it goes at different points.
| Function | Behavior |
|---|---|
ease (default) | Slow start, fast middle, slow end |
linear | Constant speed throughout |
ease-in | Slow start, fast end |
ease-out | Fast start, slow end |
ease-in-out | Slow start, slow end |
cubic-bezier(x1, y1, x2, y2) | Custom curve |
Custom Curves with cubic-bezier()
For total control, define your own curve. Useful values:
/* Snappy "pop" effect */
transition-timing-function: cubic-bezier(0.68, -0.55, 0.27, 1.55);
/* Smooth deceleration */
transition-timing-function: cubic-bezier(0.25, 0.46, 0.45, 0.94);
Tool Tip: Use cubic-bezier.com to visually design and preview custom timing curves.
4. Keyframe Animations: Multi-Step Sequences
While transitions go from A to B, @keyframes animations can have as many steps as you want:
@keyframes slideIn {
0% { transform: translateX(-100%); opacity: 0; }
50% { opacity: 0.5; }
100% { transform: translateX(0); opacity: 1; }
}
.element {
animation: slideIn 0.5s ease-out;
}
The animation Shorthand
animation: name duration timing-function delay iteration-count direction fill-mode;
/* Example */
animation: bounce 1s ease-in-out 0s infinite alternate both;
| Property | Values | Default |
|---|---|---|
animation-name | keyframe name | none |
animation-duration | time (e.g., 0.5s) | 0s |
animation-timing-function | ease, linear, etc. | ease |
animation-delay | time | 0s |
animation-iteration-count | number or infinite | 1 |
animation-direction | normal, reverse, alternate | normal |
animation-fill-mode | none, forwards, backwards, both | none |
5. animation-fill-mode: Before & After States
This property controls how an element looks before the animation starts and after it ends.
| Value | Before Animation | After Animation |
|---|---|---|
none (default) | Original styles | Original styles |
forwards | Original styles | Stays at last keyframe |
backwards | Takes 0% keyframe | Original styles |
both | Takes 0% keyframe | Stays at last keyframe |
.fade-in {
opacity: 0;
animation: fadeIn 0.5s ease forwards;
/* 'forwards' keeps opacity: 1 after animation ends */
}
@keyframes fadeIn {
to { opacity: 1; }
}
6. animation-direction: Reverse & Alternate
| Value | Behavior |
|---|---|
normal | 0% → 100% every time |
reverse | 100% → 0% every time |
alternate | 0% → 100%, then 100% → 0% (ping-pong) |
alternate-reverse | Starts at 100% and alternates |
alternate is perfect for yoyo/pulsing effects:
.breathe {
animation: breathe 2s ease-in-out infinite alternate;
}
@keyframes breathe {
from { transform: scale(1); }
to { transform: scale(1.05); }
}
7. Multiple Animations
You can apply multiple animations to one element:
.element {
animation:
fadeIn 0.3s ease,
slideUp 0.5s ease-out,
pulse 2s ease-in-out 1s infinite;
}
Each animation can have its own duration, timing, delay, and iteration count.
8. Performance: What to Animate
This is arguably the most important section. Not all CSS properties perform equally during animation.
The Performance Tiers
| Tier | Properties | Cost | What the Browser Does |
|---|---|---|---|
| 🟢 Best | transform, opacity | Cheapest | GPU compositing only |
| 🟡 Okay | color, background-color | Medium | Repaint (no layout shift) |
| 🔴 Avoid | width, height, margin, top, left | Expensive | Full layout recalculation |
Why transform & opacity Win
When you animate transform or opacity, the browser promotes the element to its own GPU layer and can animate it independently without affecting the rest of the page. This is called compositing.
When you animate width or height, the browser has to recalculate the layout of every element that's affected — this is called layout thrashing and causes jank (dropped frames).
/* ❌ Causes layout thrashing */
.bad {
transition: left 0.3s, top 0.3s;
}
.bad:hover {
left: 50px;
top: 50px;
}
/* ✅ Smooth GPU-accelerated animation */
.good {
transition: transform 0.3s;
}
.good:hover {
transform: translate(50px, 50px);
}
will-change: Preparation Hint
The will-change property tells the browser to prepare for an animation in advance:
.card {
will-change: transform, opacity;
transition: transform 0.3s, opacity 0.3s;
}
Warning: Don't overuse
will-change. Each element withwill-changegets its own GPU layer, which consumes memory. Only use it on elements that will actually animate, and remove it when the animation is done.
9. Transition Events in JavaScript
You can listen for transition completion in JavaScript:
element.addEventListener('transitionend', (e) => {
console.log(`${e.propertyName} finished transitioning`);
// Do something after animation completes
});
// Also available: transitionstart, transitioncancel
Animation Events
element.addEventListener('animationstart', handler);
element.addEventListener('animationiteration', handler); // Each loop
element.addEventListener('animationend', handler);
10. Practical Animation Patterns
Pattern 1: Hover Lift Effect
.card {
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.card:hover {
transform: translateY(-4px);
box-shadow: 0 12px 24px rgba(0, 0, 0, 0.15);
}
Pattern 2: Loading Spinner
@keyframes spin {
to { transform: rotate(360deg); }
}
.spinner {
width: 32px;
height: 32px;
border: 3px solid #e5e7eb;
border-top-color: #3b82f6;
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
Pattern 3: Staggered Entry Animation
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.card:nth-child(1) { animation: fadeInUp 0.5s ease both; }
.card:nth-child(2) { animation: fadeInUp 0.5s ease 0.1s both; }
.card:nth-child(3) { animation: fadeInUp 0.5s ease 0.2s both; }
11. prefers-reduced-motion: Respecting User Preferences
Some users experience motion sickness or distraction from animations. Always respect the prefers-reduced-motion media query:
/* Default: animations are on */
.element {
animation: fadeIn 0.5s ease;
transition: transform 0.3s;
}
/* User prefers reduced motion: disable/simplify */
@media (prefers-reduced-motion: reduce) {
.element {
animation: none;
transition: none;
}
}
Or use a progressive enhancement approach:
/* No animations by default */
.element {
opacity: 1;
}
/* Only add animations if user is okay with motion */
@media (prefers-reduced-motion: no-preference) {
.element {
animation: fadeIn 0.5s ease;
}
}
12. Common Mistakes & Gotchas
Mistake 1: Animating Layout Properties
/* ❌ Expensive — triggers layout recalculation */
.card:hover { height: 300px; width: 400px; }
/* ✅ Cheap — GPU-composited */
.card:hover { transform: scale(1.05); }
Mistake 2: Forgetting animation-fill-mode
Your animated element snaps back to its original state? You probably need forwards:
.fade-in {
opacity: 0;
animation: fadeIn 0.3s ease forwards; /* ← key! */
}
Mistake 3: Using transition: all
/* ❌ Everything transitions, even things you didn't intend */
.card { transition: all 0.3s; }
/* ✅ Only transition what you need */
.card { transition: transform 0.3s, box-shadow 0.3s; }
Mistake 4: No Reduced Motion Fallback
Always check: @media (prefers-reduced-motion: reduce). It's an accessibility requirement.
Conclusion
Here's your animation decision framework:
- Simple A→B change? → Use
transition - Multi-step sequence? → Use
@keyframes+animation - Need it forever? →
animation-iteration-count: infinite - Keep final state? →
animation-fill-mode: forwards - Performance critical? → Animate only
transformandopacity - Accessibility? → Always add
prefers-reduced-motionsupport
Remember: The best animations are the ones users don't consciously notice. They should make the interface feel natural and responsive, not draw attention to themselves.
🧠 Test Your Knowledge
Now that you've learned the concepts, let's see if you can apply them! Take this quick quiz to test your understanding.