Skip to main content

CSS Animations & Transitions: Everything You Need to Know

Master CSS animations and transitions for smooth, performant web interactions and visual effects

Note Content

Content

# CSS Animations & Transitions

Understanding Motion in CSS

Transitions vs Animations:
  • Transitions: Smooth change between two states (hover, focus, class change). Perfect for micro-interactions.
  • Animations: Complex, multi-step sequences that can loop and reverse. Ideal for attention-grabbing effects.
Performance Impact: Motion should enhance, not hinder. Stick to GPU-accelerated properties (transform, opacity) for 60fps smoothness.

Transitions

Basic Transition Syntax

What it does: Smoothly animates property changes over time instead of instant jumps. Why use it: Creates polished, professional interfaces. Users perceive smooth transitions as higher quality. How it works:
1. Browser detects property change (hover, class addition, etc.)
2. Calculates intermediate values between start and end states
3. Updates property gradually over specified duration
.element {
/ Shorthand: property duration timing-function delay /
transition: opacity 0.3s ease-in-out 0s;

/ Individual properties /
transition-property: opacity;
transition-duration: 0.3s;
transition-timing-function: ease-in-out;
transition-delay: 0s;

/ Multiple properties /
transition: opacity 0.3s, transform 0.5s;

/ All properties /
transition: all 0.3s ease;
}

Transition Properties

What it does: Controls which properties animate, how long, and with what easing. Why use it: Different properties need different speeds. Color changes feel good at 0.3s, transforms at 0.2s. How it works:
1. Define initial state with base styles
2. Set up transition rules
3. Define end state (hover, active, class)
4. Browser interpolates between states
.button {
/ Initial state /
background: #3498db;
transform: translateY(0);
box-shadow: 0 2px 4px rgba(0,0,0,0.1);

/ Transition setup - notice different durations /
transition:
background 0.3s ease, / Slower for color perception /
transform 0.2s ease, / Snappy for movement /
box-shadow 0.2s ease; / Match transform for cohesion /
}

.button:hover {
/ End state /
background: #2980b9; / Darker shade /
transform: translateY(-2px); / Subtle lift /
box-shadow: 0 4px 8px rgba(0,0,0,0.2); / Enhanced shadow /
}

Common pitfall: Using transition: all - it's slower and can animate unexpected properties. Real-world example: This exact pattern is used in Material Design buttons, GitHub's UI, and Stripe's checkout.

Timing Functions

What it does: Controls acceleration and deceleration during transitions, mimicking real-world physics. Why use it: Linear motion looks robotic. Easing makes animations feel natural and responsive. How each works:
.timing-examples {
/ LINEAR - Constant speed /
transition-timing-function: linear;
/ Use case: Progress bars, continuous rotations /
/ Feels: Mechanical, precise /

/ EASE (default) - Slow start/end, fast middle /
transition-timing-function: ease;
/ Use case: General transitions, safe default /
/ Feels: Natural, smooth /

/ EASE-IN - Slow start, accelerates /
transition-timing-function: ease-in;
/ Use case: Elements leaving screen /
/ Feels: Building momentum /

/ EASE-OUT - Fast start, decelerates /
transition-timing-function: ease-out;
/ Use case: Elements entering screen /
/ Feels: Coming to rest naturally /

/ EASE-IN-OUT - Slow both ends /
transition-timing-function: ease-in-out;
/ Use case: Looping animations, modals /
/ Feels: Gentle, considered /

/ STEPS - Discrete jumps /
transition-timing-function: steps(4, jump-start);
/ Use case: Sprite animations, typewriter effect /
/ Feels: Retro, digital /

/ CUBIC BEZIER - Custom curves /
transition-timing-function: cubic-bezier(0.68, -0.55, 0.265, 1.55);
/ This creates "overshoot" - element goes past target then settles /
/ Use case: Playful interfaces, attention-grabbing /
/ Tool: cubic-bezier.com for visual editing /
}

Pro tip: Use ease-out for entering elements (feels responsive) and ease-in for exiting (feels intentional).

Advanced Transition Patterns

Staggered Transitions What it does: Animates multiple elements in sequence, creating a cascade effect. Why use it: Draws attention to content hierarchy, makes multiple elements less overwhelming. How it works: Each element has an incremental delay, creating a wave-like appearance.
/ Initial hidden state /
.card {
opacity: 0;
transform: translateY(20px);
transition: all 0.3s ease;
}

/ Stagger delays - creates cascade /
.card:nth-child(1) { transition-delay: 0.1s; }
.card:nth-child(2) { transition-delay: 0.2s; }
.card:nth-child(3) { transition-delay: 0.3s; }

/ For dynamic lists, use CSS custom properties /
.card {
transition-delay: calc(var(--index) * 0.1s);
}

/ Trigger: Add class to parent /
.cards-visible .card {
opacity: 1;
transform: translateY(0);
}

Common pitfall: Too much delay makes interface feel sluggish. Keep total animation under 1 second. Real-world example: Google Material Design cards, Stripe's pricing page, Medium's article listings.

Animations

Keyframes Definition

/ Basic keyframes /
@keyframes fade-in {
from {
opacity: 0;
}
to {
opacity: 1;
}
}

/ Percentage-based keyframes /
@keyframes slide-bounce {
0% {
transform: translateX(0);
}
25% {
transform: translateX(100px);
}
50% {
transform: translateX(100px) translateY(-20px);
}
75% {
transform: translateX(100px) translateY(0);
}
100% {
transform: translateX(0);
}
}

Animation Properties

.animated-element {
/ Shorthand /
animation: slide-bounce 2s ease-in-out infinite alternate;

/ Individual properties /
animation-name: slide-bounce;
animation-duration: 2s;
animation-timing-function: ease-in-out;
animation-delay: 0.5s;
animation-iteration-count: infinite; / or number /
animation-direction: alternate; / normal | reverse | alternate | alternate-reverse /
animation-fill-mode: forwards; / none | forwards | backwards | both /
animation-play-state: running; / running | paused /
}

Multiple Animations

.multi-animated {
animation:
rotate 2s linear infinite,
pulse 1s ease-in-out infinite,
float 3s ease-in-out infinite;
}

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

@keyframes pulse {
50% { transform: scale(1.1); }
}

@keyframes float {
50% { transform: translateY(-10px); }
}

Common Animation Patterns

Loading Spinner

.spinner {
width: 40px;
height: 40px;
border: 4px solid #f3f3f3;
border-top: 4px solid #3498db;
border-radius: 50%;
animation: spin 1s linear infinite;
}

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

Pulse Effect

.pulse {
animation: pulse-effect 2s infinite;
}

@keyframes pulse-effect {
0% {
box-shadow: 0 0 0 0 rgba(52, 152, 219, 0.7);
}
70% {
box-shadow: 0 0 0 20px rgba(52, 152, 219, 0);
}
100% {
box-shadow: 0 0 0 0 rgba(52, 152, 219, 0);
}
}

Typing Effect

.typing {
width: 0;
overflow: hidden;
white-space: nowrap;
border-right: 3px solid;
animation:
typing 3.5s steps(40, end),
blink 0.75s step-end infinite;
}

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

@keyframes blink {
50% { border-color: transparent; }
}

Skeleton Loading

.skeleton {
background: linear-gradient(
90deg,
#f0f0f0 25%,
#e0e0e0 50%,
#f0f0f0 75%
);
background-size: 200% 100%;
animation: skeleton-loading 1.5s infinite;
}

@keyframes skeleton-loading {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}

Bounce Animation

.bounce {
animation: bounce 2s ease infinite;
}

@keyframes bounce {
0%, 20%, 50%, 80%, 100% {
transform: translateY(0);
}
40% {
transform: translateY(-30px);
}
60% {
transform: translateY(-15px);
}
}

Performance Optimization

Hardware Acceleration

What it does: Offloads animations to GPU, achieving 60fps even on complex animations. Why it matters: Janky animations frustrate users and make apps feel broken. GPU acceleration = smooth motion. How it works:
  • GPU handles: transform, opacity, filter
  • CPU handles: width, height, top, left, margin, padding
  • GPU creates compositor layers for animated elements
/ ✅ GOOD - GPU accelerated properties /
.performant {
transform: translateX(100px); / Position via transform /
opacity: 0.5; / Transparency /
filter: blur(5px); / Visual effects /
/ These run at 60fps without affecting other elements /
}

/ ❌ AVOID - Triggers expensive layout/paint /
.expensive {
left: 100px; / Forces layout recalculation /
width: 200px; / All elements must reflow /
height: 100px; / Entire page might repaint /
/ These cause "layout thrashing" - major performance hit /
}

/ Real example: Sliding panel /
/ ❌ Bad - animates 'left' property /
.panel-slow {
position: absolute;
left: -300px;
transition: left 0.3s;
}
.panel-slow.open {
left: 0;
}

/ ✅ Good - animates 'transform' /
.panel-fast {
transform: translateX(-300px);
transition: transform 0.3s;
}
.panel-fast.open {
transform: translateX(0);
}

Common pitfall: Animating width/height for accordions. Use max-height or scale transform instead. Performance test: Chrome DevTools > Rendering tab > Enable "Paint flashing" to see what's repainting.

Will-Change Property

.will-animate {
will-change: transform, opacity;
}

/ Remove after animation /
.animation-done {
will-change: auto;
}

Reducing Paint Areas

.animated-card {
/ Isolate paint area /
contain: layout style paint;

/ Create layer /
transform: translateZ(0);

/ Or /
will-change: transform;
}

Interactive Examples

Hover Card Flip

.flip-card {
width: 300px;
height: 200px;
perspective: 1000px;
}

.flip-card-inner {
position: relative;
width: 100%;
height: 100%;
transition: transform 0.6s;
transform-style: preserve-3d;
}

.flip-card:hover .flip-card-inner {
transform: rotateY(180deg);
}

.flip-card-front, .flip-card-back {
position: absolute;
width: 100%;
height: 100%;
backface-visibility: hidden;
}

.flip-card-back {
transform: rotateY(180deg);
}

Morphing Button

.morph-button {
padding: 12px 24px;
background: #3498db;
color: white;
border: none;
border-radius: 4px;
position: relative;
overflow: hidden;
transition: all 0.3s;
}

.morph-button::before {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 0;
height: 0;
border-radius: 50%;
background: rgba(255,255,255,0.3);
transform: translate(-50%, -50%);
transition: width 0.6s, height 0.6s;
}

.morph-button:hover::before {
width: 300px;
height: 300px;
}

Animated Gradient Background

.gradient-bg {
background: linear-gradient(
-45deg,
#ee7752,
#e73c7e,
#23a6d5,
#23d5ab
);
background-size: 400% 400%;
animation: gradient-shift 15s ease infinite;
}

@keyframes gradient-shift {
0% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
100% { background-position: 0% 50%; }
}

Animation State Management

Play/Pause Control

.video-animation {
animation: rotate 2s linear infinite;
animation-play-state: paused;
}

.playing .video-animation {
animation-play-state: running;
}

Animation Events in CSS

/ Using animation-fill-mode /
.slide-in {
opacity: 0;
transform: translateX(-100%);
animation: slide-in 0.5s forwards;
}

@keyframes slide-in {
to {
opacity: 1;
transform: translateX(0);
}
}

Accessibility Considerations

Respecting User Preferences

What it does: Detects if users have requested reduced motion in their OS settings. Why it matters: Motion can trigger vestibular disorders, migraines, and motion sickness. It's not just preference - it's accessibility. How to implement: Provide alternatives, not just removal. Replace motion with fades or instant changes.
/ Method 1: Nuclear option - remove all motion /
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}

/ Method 2: Better - provide alternatives /
.card {
animation: slideUp 0.5s ease;
}

@media (prefers-reduced-motion: reduce) {
.card {
animation: fadeIn 0.2s ease; / Gentler alternative /
}
}

/ Method 3: Best - design with preference in mind /
.animated-element {
/ Default: subtle motion /
transform: scale(1);
transition: transform 0.2s ease;
}

.animated-element:hover {
transform: scale(1.05); / Small, purposeful /
}

@media (prefers-reduced-motion: no-preference) {
/ Enhance for those who want motion /
.animated-element {
transition: transform 0.3s cubic-bezier(0.68, -0.55, 0.265, 1.55);
}
.animated-element:hover {
transform: scale(1.1) rotate(5deg);
}
}

Testing:
  • macOS: System Preferences > Accessibility > Display > Reduce motion
  • Windows: Settings > Ease of Access > Display > Show animations
  • Chrome DevTools: Rendering tab > Emulate CSS media feature prefers-reduced-motion
Real-world impact: iOS automatically reduces parallax and animations when enabled. Major sites like GitHub respect this setting.

Focus Indicators

.interactive:focus-visible {
outline: none;
animation: focus-pulse 1s ease;
}

@keyframes focus-pulse {
0% {
box-shadow: 0 0 0 0 rgba(52, 152, 219, 0.7);
}
100% {
box-shadow: 0 0 0 8px rgba(52, 152, 219, 0);
}
}

Navigation