Web Animations with CSS – From Basics to Advanced Techniques
URL: https://ruvebal.github.io/web-atelier-udit/lessons/en/web-animations/css/
📋 Table of Contents
- ⏰ Estimated Duration
- 🎯 Learning Objectives
- 🎭 Atelier Approach: Critical Coding with Motion
- 📖 Introduction
- 🎓 Part I: Foundational Techniques
- 🚀 Part II: Advanced Techniques
- 🎯 When CSS Animations Need JavaScript Triggers
- The Problem: Animations Without Triggers
- Solution 1: Intersection Observer API (Recommended)
- Solution 2: CSS-Only Alternatives (Limited Cases)
- Solution 3: Legacy Scroll Listeners (Avoid if Possible)
- Common Animation Trigger Patterns
- Practical Example: Staggered Card Reveal
- Browser Support & Polyfills
- Debugging Tips
- Key Takeaways
- 🎨 Design Principles for Motion
- ⚡ Performance Considerations
- ♿ Accessibility: Respecting User Preferences
- 🛠️ Practice Exercise: Animate Your Portfolio
- 📚 Further Resources
- 🎯 Summary & Key Takeaways
- 💬 Reflection Questions
- 🎪 Exhibition: Share Your Work
⏰ Estimated Duration
120 minutes (2 sessions or 1 extended workshop)
🎯 Learning Objectives
By the end of this lesson, you will be able to:
- Explore CSS animation capabilities through hands-on experimentation
- Apply transitions, keyframes, and transforms to create engaging user experiences
- Understand the performance implications of different animation properties
- Implement advanced techniques like SVG animations and scroll-driven effects
- Evaluate when to use CSS vs JavaScript for animations
- Create accessible, performant animations that respect user preferences
🎭 Atelier Approach: Critical Coding with Motion
Following our atelier methodology, this lesson embraces critical coding through a five-phase cycle:
- 🔍 Exploration → Experiment with each animation technique in isolated demos
- 💭 Reflection → Understand why animations work and their impact on UX
- 📚 Conceptualization → Connect practice to design theory, performance, and accessibility
- 🛠️ Production → Build animations for your personal project
- 🎪 Exhibition → Share and critique animation choices in your work
Motion is not decoration—it’s communication. Every animation should have a purpose: guide attention, provide feedback, or enhance understanding.
📖 Introduction
CSS3 animations enable web developers and designers to create smooth, dynamic visual effects directly in CSS, often eliminating the need for JavaScript. This lesson covers foundational techniques and advanced methods, ensuring you can make informed decisions about when and how to animate.
🎓 Part I: Foundational Techniques
These core animation techniques form the building blocks of web motion design. Master these before moving to advanced methods.
1. Transitions
📂 Demo: demo/01-transitions.html
- What It Is: A CSS feature that smoothly animates changes in property values over a specified duration.
- How It Works: You specify which properties should transition (e.g.,
background-color) and how long the animation should last. - Example:
- HTML: A box that changes color when hovered.
- CSS:
.box { background-color: blue; transition: background-color 0.5s ease; } .box:hover { background-color: red; } - Key Points:
transitiondefines the duration and easing of the animation.- Triggered by events like
hoverorfocus.
2. Keyframes
📂 Demo: demo/02-keyframes.html
- What It Is: A CSS rule (
@keyframes) used to define intermediate steps in an animation. - How It Works: You create keyframes that specify how an element should look at various points in time during the animation.
- Example:
- HTML: A box sliding in from the left.
- CSS:
@keyframes slideIn { from { transform: translateX(-100%); } to { transform: translateX(0); } } .box { animation: slideIn 1s ease; } - Key Points:
- Use
@keyframesto define the animation behavior. - Apply the animation using the
animationproperty, specifying the name, duration, and easing.
- Use
3. Transformations
📂 Demo: demo/03-transformations.html
- What It Is: The
transformproperty modifies the shape, size, position, or rotation of an element. - How It Works: You can rotate, scale, skew, or translate an element, often combined with transitions or animations.
- Example:
- HTML: A box rotates and scales when hovered.
- CSS:
.box:hover { transform: rotate(45deg) scale(1.2); transition: transform 0.5s; } - Key Points:
rotate(deg)rotates an element.scale(x, y)resizes the element.transitionmakes the transformation smooth.
4. Infinite Animations
📂 Demo: demo/04-infinite-animations.html
- What It Is: Animations that loop indefinitely using the
animation-iteration-count: infiniteproperty. - How It Works: Define the animation using keyframes, then set it to repeat.
- Example:
- HTML: A spinning box.
- CSS:
@keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } .box { animation: spin 2s linear infinite; } - Key Points:
- Use
infiniteto loop the animation. - Control the speed and easing with
animation-durationandanimation-timing-function.
- Use
🚀 Part II: Advanced Techniques
These sophisticated methods are for students ready to push beyond basics and create production-quality, performant animations.
5. Text Animations
📂 Demo: demo/05-text-animations.html
- What It Is: Applying animations to text properties like
text-shadow, color, or position to make text visually dynamic. - How It Works: Define a glowing effect, typewriter animation, or fade-in using keyframes.
- Example:
- HTML: Text glows in and out.
- CSS:
@keyframes glow { 0%, 100% { text-shadow: 0 0 10px blue; } 50% { text-shadow: 0 0 20px cyan; } } .text { animation: glow 1.5s ease-in-out infinite; } - Key Points:
- Use
text-shadowfor glowing effects. - Combine
infiniteand easing for smooth, looping animations.
- Use
6. Background Animations
📂 Demo: demo/06-background-animations.html
- What It Is: Animate background properties like color, gradient, or position.
- How It Works: Use
background-positionwith keyframes to create moving effects. - Example:
- HTML: A gradient background moves continuously.
- CSS:
@keyframes backgroundMove { from { background-position: 0 0; } to { background-position: 200% 0; } } .box { background: linear-gradient(to right, red, yellow, green, blue); background-size: 200% 100%; animation: backgroundMove 3s linear infinite; } - Key Points:
- Use gradients and
background-sizefor smooth movement. background-positionshifts the gradient over time.
- Use gradients and
7. Staggered Animations
📂 Demo: demo/07-staggered-animations.html
- What It Is: Delaying animations for multiple elements to create a sequenced effect.
- How It Works: Use
animation-delayfor each child element to start their animations at different times. - Example:
- HTML: A set of boxes animating one after another.
- CSS:
.box:nth-child(1) { animation-delay: 0s; } .box:nth-child(2) { animation-delay: 0.2s; } .box:nth-child(3) { animation-delay: 0.4s; } @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } .box { animation: fadeIn 1s ease-in; } - Key Points:
nth-child(n)applies styles to specific child elements.animation-delaycreates staggered effects.
8. Morphing Shapes
📂 Demo: demo/08-morphing-shapes.html
- What It Is: Animates an element’s shape by modifying properties like
border-radiusorclip-path. - How It Works: Use
border-radiusin keyframes to change the shape over time. - Example:
- HTML: A box morphing into a circle and back.
- CSS:
@keyframes morph { 0% { border-radius: 0%; } 50% { border-radius: 50%; } 100% { border-radius: 0%; } } .box { animation: morph 2s infinite; } - Key Points:
border-radiuschanges a square into a circle.- Keyframes allow complex shape transitions.
9. SVG Animations
📂 Demo: demo/09-svg-animations.html
- What It Is: Animating Scalable Vector Graphics using CSS or SMIL (Synchronized Multimedia Integration Language).
- How It Works: Apply animations to SVG paths, shapes, and attributes for resolution-independent motion.
- Example:
- SVG: Animated logo with path drawing effect.
- CSS:
.logo-path { stroke-dasharray: 1000; stroke-dashoffset: 1000; animation: draw 3s ease-in-out forwards; } @keyframes draw { to { stroke-dashoffset: 0; } } - Key Points:
stroke-dasharrayandstroke-dashoffsetcreate the drawing effect.- SVGs scale perfectly on all screens.
- Great for icons, illustrations, and logos.
💡 Beyond CSS for SVG Animations: While CSS can animate SVG properties, complex SVG morphing, sequencing, and physics-based animations benefit significantly from JavaScript animation libraries like GSAP (GreenSock Animation Platform). GSAP provides:
- MorphSVG Plugin: Smoothly morph between completely different SVG shapes
- DrawSVG Plugin: More control over path drawing than CSS
stroke-dashoffset- Timeline Sequencing: Orchestrate complex multi-element choreography
- Cross-browser Consistency: Eliminates CSS animation quirks across browsers
- Physics & Easing: Advanced easing functions and physics-based motion
When to use CSS vs GSAP for SVG:
- CSS: Simple transforms, opacity, basic path drawing, hover states
- GSAP: Complex morphing, sequenced choreography, interactive animations, production-grade projects
See our GSAP Animation lesson for advanced techniques.
10. Scroll-Driven Animations
📂 Demo: demo/10-scroll-driven.html
- What It Is: Modern CSS animations that respond to scroll position using
animation-timeline: scroll(). - How It Works: Animations progress based on viewport scroll rather than time.
- Example:
- HTML: Reading progress bar or parallax hero section.
- CSS:
@supports (animation-timeline: scroll()) { .progress-bar { animation: grow linear; animation-timeline: scroll(); } @keyframes grow { from { transform: scaleX(0); } to { transform: scaleX(1); } } } - Key Points:
- New feature with growing browser support (progressive enhancement).
- Falls back gracefully with JavaScript for older browsers.
- Reduces JavaScript for scroll-based effects.
🎯 When CSS Animations Need JavaScript Triggers
📂 Demo: demo/11-intersection-observer.html
While CSS provides powerful animation capabilities, most animations don’t start automatically—they need to be triggered. Understanding when and how to trigger animations is crucial for creating effective, performant user experiences.
The Problem: Animations Without Triggers
Consider this common scenario:
.feature-card {
opacity: 0;
animation: fadeInUp 0.8s ease-out forwards;
}
@keyframes fadeInUp {
to {
opacity: 1;
transform: translateY(0);
}
}
Issue: This animation starts immediately when the page loads, even if the .feature-card is far down the page where users can’t see it. By the time users scroll to it, the animation has already finished.
Solution 1: Intersection Observer API (Recommended)
The Intersection Observer API is the modern, performant way to trigger animations when elements enter the viewport.
How It Works
// 1. Define what happens when element is visible
const observerOptions = {
root: null, // viewport
rootMargin: '0px',
threshold: 0.1, // trigger when 10% visible
};
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
// Element is visible - add animation class
entry.target.classList.add('animate-in');
// Optional: stop observing after animation triggers once
observer.unobserve(entry.target);
}
});
}, observerOptions);
// 2. Observe all elements you want to animate
document.querySelectorAll('.feature-card').forEach((card) => {
observer.observe(card);
});
CSS Setup
/* Initial state: invisible and positioned */
.feature-card {
opacity: 0;
transform: translateY(30px);
transition: opacity 0.8s ease-out, transform 0.8s ease-out;
}
/* Triggered state: visible and in place */
.feature-card.animate-in {
opacity: 1;
transform: translateY(0);
}
Why Intersection Observer?
✅ Performant: Runs on the main thread but doesn’t require scroll listeners ✅ Battery-friendly: Only checks when elements cross visibility boundaries ✅ Flexible: Configure visibility thresholds, root margins, and multiple roots ✅ Clean: Separates observation logic from animation styling ✅ Reliable: Works correctly with lazy-loaded content and dynamic DOM changes
Solution 2: CSS-Only Alternatives (Limited Cases)
A. Scroll-Driven Animations (Modern Browsers)
@supports (animation-timeline: view()) {
.feature-card {
animation: fadeInUp linear;
animation-timeline: view();
animation-range: entry 0% cover 30%;
}
}
Pros: No JavaScript required Cons: Limited browser support (Chrome 115+), less control over trigger point
B. :hover, :focus, :active States
.button {
transition: transform 0.3s;
}
.button:hover {
transform: scale(1.05);
}
Good for: User-initiated interactions Not suitable for: Scroll-based reveals, page load animations
Solution 3: Legacy Scroll Listeners (Avoid if Possible)
Before Intersection Observer, developers used scroll event listeners:
// ⚠️ NOT RECOMMENDED - Performance issues
window.addEventListener('scroll', () => {
const cards = document.querySelectorAll('.feature-card');
cards.forEach((card) => {
const rect = card.getBoundingClientRect();
if (rect.top < window.innerHeight) {
card.classList.add('animate-in');
}
});
});
Problems:
- ❌ Fires continuously during scroll (100+ times per second)
- ❌ Causes layout thrashing if not debounced properly
- ❌ Drains battery on mobile devices
- ❌ Blocks main thread execution
Modern replacement: Use Intersection Observer instead.
Common Animation Trigger Patterns
| Use Case | Best Solution | Implementation |
|---|---|---|
| Element enters viewport | Intersection Observer | Observe with threshold, add class on intersection |
| User interaction (hover, click) | CSS pseudo-classes | :hover, :focus, :active |
| Page load | CSS animation on load | Add class to <body> when DOM ready |
| Scroll progress | Scroll-driven CSS (modern) | animation-timeline: scroll() |
| Time-based sequence | JavaScript setTimeout |
Chain animations with delays |
| User gesture (swipe, drag) | JavaScript event handlers | touchstart, mousedown + transform |
| Form validation feedback | Class toggle on validation | Add/remove .error or .success classes |
| Modal/dialog open | CSS animation + ARIA | Toggle .is-open class with aria-hidden attribute |
Practical Example: Staggered Card Reveal
<div class="card-grid">
<article class="card" data-delay="0">Card 1</article>
<article class="card" data-delay="100">Card 2</article>
<article class="card" data-delay="200">Card 3</article>
</div>
.card {
opacity: 0;
transform: translateY(30px);
transition: opacity 0.6s ease-out, transform 0.6s ease-out;
}
.card.animate-in {
opacity: 1;
transform: translateY(0);
}
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
const delay = entry.target.dataset.delay || 0;
setTimeout(() => {
entry.target.classList.add('animate-in');
}, delay);
observer.unobserve(entry.target);
}
});
},
{ threshold: 0.2 }
);
document.querySelectorAll('.card').forEach((card) => observer.observe(card));
Browser Support & Polyfills
Intersection Observer: Supported in all modern browsers (IE11 requires polyfill)
<!-- Polyfill for older browsers -->
<script src="https://polyfill.io/v3/polyfill.min.js?features=IntersectionObserver"></script>
Scroll-driven animations: Chrome/Edge 115+, limited support elsewhere
/* Progressive enhancement with feature detection */
@supports (animation-timeline: scroll()) {
/* Modern browsers get smooth scroll animations */
}
@supports not (animation-timeline: scroll()) {
/* Fallback for older browsers (maybe Intersection Observer) */
}
Debugging Tips
- Visualize Intersection Observer thresholds:
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
console.log(`${entry.target.id}: ${entry.intersectionRatio.toFixed(2)} visible`);
});
},
{ threshold: [0, 0.25, 0.5, 0.75, 1] }
);
- Add debug borders to see observer root margins:
.observed-element {
outline: 2px dashed red; /* Remove after debugging */
}
- Test with reduced motion to ensure fallbacks work:
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
if (prefersReducedMotion) {
// Skip animations or use instant transitions
document.body.classList.add('reduce-motion');
}
Key Takeaways
- CSS defines HOW elements animate, JavaScript decides WHEN
- Intersection Observer is the standard for viewport-triggered animations
- Scroll listeners are legacy and should be avoided for animation triggers
- Respect user preferences with
prefers-reduced-motion - Progressive enhancement: Modern CSS scroll-driven animations with JS fallbacks
- Separation of concerns: Keep animation styling in CSS, triggering logic in JS
🎨 Design Principles for Motion
When to Animate
✅ Good Reasons:
- Feedback: Confirm user actions (button clicks, form submissions)
- Guidance: Direct attention to important elements
- Continuity: Show spatial or hierarchical relationships
- Personality: Reinforce brand identity
❌ Avoid Animating:
- Just for decoration without purpose
- Elements that distract from core content
- Critical UI that users need immediately
Duration Guidelines
| Duration | Use Case | Example |
|---|---|---|
| 100-200ms | Micro-interactions | Button hover, checkbox toggle |
| 200-500ms | UI transitions | Modal open, dropdown menu |
| 500-1000ms | Page transitions | Route changes, hero animations |
| 1000ms+ | Ambient animations | Background patterns, loading states |
⚡ Performance Considerations
Hardware-Accelerated Properties
Fast (GPU-accelerated):
transform(translate, scale, rotate, skew)opacityfilter(with caution)
Slow (triggers layout/paint):
top,left,right,bottomwidth,heightmargin,paddingbackground-position(usetransforminstead)
Best Practices
/* ✅ GOOD: GPU-accelerated */
.element {
transform: translateY(20px);
opacity: 0;
transition: transform 0.3s, opacity 0.3s;
}
/* ❌ BAD: Causes reflow */
.element {
top: 20px;
display: none;
transition: top 0.3s;
}
/* ✅ GOOD: Specify what to animate */
.button {
transition: background-color 0.2s ease-in-out;
}
/* ❌ BAD: Animates everything */
.button {
transition: all 0.2s;
}
♿ Accessibility: Respecting User Preferences
Always respect the prefers-reduced-motion media query for users with vestibular disorders or motion sensitivity:
/* Default: full animations */
.animated {
animation: slide 1s ease-in-out;
}
/* Respect user preference */
@media (prefers-reduced-motion: reduce) {
.animated {
animation: none;
/* Provide instant state change instead */
}
* {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
}
}
Accessibility Checklist:
- ✅ Provide reduced-motion fallbacks
- ✅ Ensure animations don’t prevent access to content
- ✅ Don’t rely solely on animation to convey information
- ✅ Avoid flashing content that could trigger seizures (< 3 flashes/sec)
🛠️ Practice Exercise: Animate Your Portfolio
Task: Apply animations to your personal project repository.
Requirements:
-
Basic (Required):
- Add hover transitions to all interactive elements (buttons, links)
- Implement a staggered fade-in for page sections on load
- Use transforms for card lift effects
-
Advanced (Optional):
- Create a custom loading animation
- Add scroll-driven reading progress bar
- Animate SVG logo with path drawing
- Implement morphing shapes for navigation icons
-
Documentation:
- Commit with message:
feat: add CSS animations to [component] - Document animation choices in your project journal
- Explain why each animation serves the user experience
- Commit with message:
Evaluation Criteria:
| Criterion | Weight | Description |
|---|---|---|
| Purpose | 30% | Animations serve UX goals, not just decoration |
| Performance | 25% | Uses GPU-accelerated properties |
| Accessibility | 25% | Respects prefers-reduced-motion |
| Execution | 20% | Smooth, polished, bug-free |
📚 Further Resources
References
- MDN Web Docs: CSS Animations
- Web Animation Performance: Google Developers Guide
- Scroll-Driven Animations: Chrome Developer Blog
Inspiration
- CSS Animation Rocks
- Codrops CSS Reference
- UI Movement - Design animation patterns
Tools
- Cubic Bezier Generator - Custom easing curves
- Animista - Ready-made animation library
- Keyframes.app - Visual animation editor
🎯 Summary & Key Takeaways
Essential Concepts
- Transitions smooth property changes triggered by state (hover, focus)
- Keyframes define multi-step animations with explicit control
- Transforms manipulate elements without triggering layout recalculation
- Performance matters: use
transformandopacityfor smooth 60fps animations - Accessibility is not optional: respect user preferences and provide fallbacks
Pro Tips
- Start subtle: Animations should enhance, not overwhelm
- Design with intent: Every animation should answer “why?”
- Test on devices: What runs smoothly on desktop may lag on mobile
- Iterate based on feedback: Watch users interact with your animations
- Keep learning: New CSS features like View Transitions API are coming
💬 Reflection Questions
Before moving on, consider:
- Which animations in the demos felt purposeful vs purely decorative?
- How do animations affect the perceived performance of a site?
- When might JavaScript animations be more appropriate than CSS?
- How can motion design reinforce your project’s identity and values?
🎪 Exhibition: Share Your Work
Once you’ve implemented animations in your project:
- Deploy your updated site to GitHub Pages
- Document your animation strategy in your project README
- Present (2 minutes) one animation you’re proud of and explain its UX purpose
- Critique a peer’s work: Does the motion serve the user or distract?
Remember: Motion is a design tool, not a decoration. Use it wisely.
Next Lesson: GSAP Animation Mastery – From Zero to Production
License: Content CC BY-NC-SA 4.0 · Code MIT Author: Rubén Vega Balbás, PhD · UDIT