Module 10

Transitions, Animations & Transforms

Transitions • @keyframes • Transforms • Timing Functions • Performance • Accessibility

🎭 Bringing Your Pages to Life

Static pages are functional, but motion brings delight. A button that smoothly changes color on hover, a card that lifts when hovered, a loading spinner that rotates — these are the micro-interactions that make a website feel polished and professional. CSS gives you three powerful tools to create motion: transitions, transforms, and animations.

The best part: you can create stunning motion effects entirely in CSS, no JavaScript required. These CSS-native animations are hardware-accelerated, meaning they run smoothly at 60fps and don't block the main thread.

💡
The Three Pillars of CSS Motion: Transitions = smooth change from state A to state B (e.g., hover effect). Transforms = move, rotate, scale, or skew an element. Animations = multi-step sequences with full keyframe control. Together, they let you create anything from subtle hover effects to complex choreographed sequences.
⚠️
Motion can harm Excessive or unexpected motion can trigger vestibular disorders, cause nausea, or simply annoy users. Always respect prefers-reduced-motion and keep animations purposeful. We'll cover accessibility in depth later in this module.

🔄 CSS Transitions

A transition smoothly interpolates a CSS property from one value to another over a specified duration. Instead of an abrupt change — like a button instantly turning blue — the color gradually shifts over time. Transitions are the simplest form of CSS animation and the one you'll use most often.

The Four Transition Properties

CSS
/* Longhand properties */
.button {
    transition-property: background-color, transform;
    transition-duration: 0.3s;
    transition-timing-function: ease;
    transition-delay: 0s;
}

/* Shorthand — most common */
.button {
    transition: background-color 0.3s ease, transform 0.2s ease;
}

.button:hover {
    background-color: #3b82f6;
    transform: translateY(-2px);
}
Property Description Default
transition-property Which CSS properties to animate (all, or specific names) all
transition-duration How long the transition takes (s or ms) 0s
transition-timing-function Speed curve (ease, linear, ease-in, ease-out, ease-in-out, cubic-bezier) ease
transition-delay Wait time before the transition starts 0s

What Can Be Transitioned?

Not all CSS properties can be transitioned — only properties with interpolatable values. Colors, lengths, percentages, opacity, transforms — yes. display, font-family, visibility (sort of) — no. The general rule: if the browser can calculate values between the start and end, it can be transitioned.

💡
Avoid transition: all While transition: all 0.3s is convenient, it transitions every changing property — including ones you didn't intend, like width or height during layout changes. Always specify the exact properties you want to transition.

Try It: Hover Transition

CSS
Try it →
.card {
    padding: 24px;
    background: #f1f5f9;
    border-radius: 12px;
    box-shadow: 0 1px 3px rgba(0,0,0,0.1);
    transition: transform 0.3s ease, box-shadow 0.3s ease;
}

.card:hover {
    transform: translateY(-4px);
    box-shadow: 0 10px 30px rgba(0,0,0,0.15);
}

Multiple Transitions & Staggering

CSS
Try it →
.btn {
    padding: 12px 24px;
    background: #3b82f6;
    color: white;
    border: none;
    border-radius: 8px;
    font-size: 1rem;
    cursor: pointer;
    /* Different duration & delay per property */
    transition: background-color 0.2s ease,
                 transform 0.3s ease 0.05s,
                 box-shadow 0.3s ease 0.1s;
}

.btn:hover {
    background-color: #2563eb;
    transform: translateY(-2px);
    box-shadow: 0 8px 20px rgba(59,130,246,0.4);
}

🔀 CSS Transforms (2D)

The transform property lets you visually modify an element without affecting document flow. The element's original space in the layout is preserved — transforms happen in a separate compositing layer. This is why transforms are so performant: the browser doesn't need to recalculate layout.

Transform Functions

Function What it does Example
translate(x, y) Moves element horizontally and/or vertically translate(20px, -10px)
rotate(angle) Rotates clockwise (positive) or counter-clockwise (negative) rotate(45deg)
scale(x, y) Resizes element (1 = original, >1 = bigger, <1 = smaller) scale(1.2)
skew(x, y) Tilts element along X and/or Y axis skew(10deg, 5deg)
CSS
Try it →
.box {
    width: 100px;
    height: 100px;
    background: #3b82f6;
    border-radius: 8px;
    transition: transform 0.4s ease;
    display: flex;
    align-items: center;
    justify-content: center;
    color: white;
    font-weight: 600;
    margin: 24px;
    display: inline-flex;
}

.translate:hover { transform: translate(20px, -10px); }
.rotate:hover    { transform: rotate(45deg); }
.scale:hover     { transform: scale(1.3); }
.skew:hover      { transform: skew(10deg, 5deg); }

Chaining Transforms

You can apply multiple transforms in a single declaration. They are applied right to left (like matrix multiplication). Order matters!

CSS
/* Translate THEN rotate — different from rotate THEN translate */
.element {
    transform: translate(100px, 0) rotate(45deg);
}

/* Rotate THEN translate (element moves diagonally!) */
.element-alt {
    transform: rotate(45deg) translate(100px, 0);
}

Transform Origin

By default, transforms originate from the center of the element (50% 50%). You can change this with transform-origin:

CSS
Try it →
.door {
    width: 120px;
    height: 200px;
    background: linear-gradient(135deg, #8b5cf6, #6d28d9);
    border-radius: 8px;
    /* Rotate from the left edge — like a door hinge */
    transform-origin: left center;
    transition: transform 0.5s ease;
}

.door:hover {
    transform: rotateY(-60deg);
}

🧊 3D Transforms

CSS supports full 3D transformations. To see 3D effects, you need to set perspective on the parent element. Perspective defines the distance between the viewer and the z=0 plane — smaller values create more dramatic 3D effects.

CSS
Try it →
.scene {
    perspective: 600px;
    display: flex;
    gap: 40px;
    padding: 40px;
}

.card-3d {
    width: 150px;
    height: 200px;
    background: linear-gradient(135deg, #06b6d4, #3b82f6);
    border-radius: 12px;
    display: flex;
    align-items: center;
    justify-content: center;
    color: white;
    font-weight: 700;
    font-size: 0.85rem;
    transition: transform 0.5s ease;
}

.rx:hover { transform: rotateX(45deg); }
.ry:hover { transform: rotateY(45deg); }
.tz:hover { transform: translateZ(80px); }

Card Flip Pattern

A classic 3D pattern — a card with front and back faces that flips on hover:

CSS
Try it →
.flip-container {
    perspective: 1000px;
    width: 200px;
    height: 260px;
}

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

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

.flip-front, .flip-back {
    position: absolute;
    inset: 0;
    backface-visibility: hidden;
    border-radius: 16px;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 1.2rem;
    font-weight: 700;
}

.flip-front {
    background: linear-gradient(135deg, #3b82f6, #8b5cf6);
    color: white;
}

.flip-back {
    background: linear-gradient(135deg, #f59e0b, #ef4444);
    color: white;
    transform: rotateY(180deg);
}

🎬 @keyframes Animations

While transitions go from A to B, @keyframes animations let you define multi-step sequences. You control exactly what happens at each point in time. They can run automatically (no hover needed), loop infinitely, alternate directions, and much more.

Defining Keyframes

CSS
Try it →
/* Define the animation */
@keyframes bounce {
    0%, 100% {
        transform: translateY(0);
    }
    50% {
        transform: translateY(-30px);
    }
}

/* Apply it */
.ball {
    width: 60px;
    height: 60px;
    background: #ef4444;
    border-radius: 50%;
    animation: bounce 0.8s ease-in-out infinite;
}

Animation Properties

Property Description Common Values
animation-name Name of the @keyframes rule bounce, fadeIn
animation-duration Length of one cycle 0.5s, 2s
animation-timing-function Speed curve per keyframe segment ease, linear
animation-delay Wait before first cycle 0s, 0.5s
animation-iteration-count How many times to repeat 1, 3, infinite
animation-direction Play direction normal, reverse, alternate
animation-fill-mode State before/after animation none, forwards, backwards, both
animation-play-state Pause/resume running, paused

Understanding fill-mode

animation-fill-mode controls what happens before and after the animation plays:

CSS
Try it →
@keyframes slideIn {
    from {
        opacity: 0;
        transform: translateX(-60px);
    }
    to {
        opacity: 1;
        transform: translateX(0);
    }
}

.slide-item {
    padding: 16px 24px;
    background: #e0f2fe;
    border-radius: 8px;
    margin-bottom: 8px;
    animation: slideIn 0.5s ease both;
}

/* Stagger using delay */
.slide-item:nth-child(2) { animation-delay: 0.1s; }
.slide-item:nth-child(3) { animation-delay: 0.2s; }
.slide-item:nth-child(4) { animation-delay: 0.3s; }

⏱️ Timing Functions Deep Dive

The timing function controls the speed curve — how fast the animation progresses at different points. It's the difference between a robotic movement and a natural one.

Built-in Keywords

Keyword cubic-bezier Equivalent Feel
linear cubic-bezier(0, 0, 1, 1) Constant speed, robotic
ease cubic-bezier(0.25, 0.1, 0.25, 1) Slow start, fast middle, slow end (default)
ease-in cubic-bezier(0.42, 0, 1, 1) Slow start, fast end (like accelerating)
ease-out cubic-bezier(0, 0, 0.58, 1) Fast start, slow end (like decelerating)
ease-in-out cubic-bezier(0.42, 0, 0.58, 1) Slow start and end, fast middle

Custom cubic-bezier()

For full control, define your own curve with cubic-bezier(x1, y1, x2, y2). Values outside 0–1 on the Y axis create overshoot effects (bounce, elastic):

CSS
Try it →
.bounce-btn {
    padding: 14px 28px;
    background: #8b5cf6;
    color: white;
    border: none;
    border-radius: 8px;
    font-size: 1rem;
    cursor: pointer;
    /* Overshoot: goes past target then settles back */
    transition: transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
}

.bounce-btn:hover {
    transform: scale(1.1);
}

steps() for Frame-by-Frame

The steps() function creates discrete jumps instead of smooth interpolation — perfect for sprite animations or typewriter effects:

CSS
Try it →
@keyframes typing {
    from { width: 0; }
    to { width: 16ch; }
}

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

.typewriter {
    font-family: monospace;
    font-size: 1.5rem;
    overflow: hidden;
    white-space: nowrap;
    border-right: 3px solid #3b82f6;
    width: 16ch;
    animation: typing 2s steps(16) 1s both,
               blink 0.7s step-end infinite;
}

🎨 Combining Transitions, Transforms & Animations

The real power comes from combining these three tools. Transitions handle interactive state changes (hover, focus), transforms handle the visual movement, and @keyframes handle autonomous sequences.

Animated Hover Card

CSS
Try it →
@keyframes shimmer {
    0% { background-position: -200% center; }
    100% { background-position: 200% center; }
}

.fancy-card {
    padding: 32px;
    background: white;
    border-radius: 16px;
    box-shadow: 0 2px 8px rgba(0,0,0,0.08);
    border: 1px solid #e2e8f0;
    position: relative;
    overflow: hidden;
    transition: transform 0.3s ease, box-shadow 0.3s ease;
}

.fancy-card::after {
    content: '';
    position: absolute;
    inset: 0;
    background: linear-gradient(90deg,
        transparent, rgba(255,255,255,0.4), transparent);
    background-size: 200% 100%;
    opacity: 0;
    transition: opacity 0.3s ease;
}

.fancy-card:hover {
    transform: translateY(-6px);
    box-shadow: 0 16px 40px rgba(0,0,0,0.12);
}

.fancy-card:hover::after {
    opacity: 1;
    animation: shimmer 1.5s ease infinite;
}

Loading Spinner

CSS
Try it →
@keyframes spin {
    to { transform: rotate(360deg); }
}

.spinner {
    width: 48px;
    height: 48px;
    border: 4px solid #e2e8f0;
    border-top-color: #3b82f6;
    border-radius: 50%;
    animation: spin 0.8s linear infinite;
}

⚡ Animation Performance

Not all CSS properties animate equally. Understanding how the browser renders animations is crucial for keeping your site smooth at 60 frames per second.

The Rendering Pipeline

When the browser renders a frame, it goes through up to four phases: Style → Layout → Paint → Composite. The further down the pipeline a change triggers, the cheaper it is:

Phase Triggered Properties Performance Cost
Layout (reflow) width, height, margin, padding, top, left 🔴 Expensive — recalculates entire layout
Paint (repaint) color, background, box-shadow, border-color 🟡 Moderate — redraws pixels
Composite only transform, opacity 🟢 Cheap — GPU-accelerated, no layout/paint
💡
The Golden Rule Only animate transform and opacity whenever possible. Need to "move" an element? Use transform: translate() instead of changing top/left. Need to "resize"? Use transform: scale() instead of changing width/height.

will-change

The will-change property tells the browser to prepare for an animation — promoting the element to its own compositor layer ahead of time:

CSS
/* ✅ Set on hover intent, remove after */
.card {
    transition: transform 0.3s ease;
}
.card:hover {
    will-change: transform;
    transform: scale(1.05);
}

/* ❌ Don't set will-change on everything */
/* * { will-change: transform; } — wastes GPU memory */
⚠️
Don't overuse will-change Each will-change element gets its own compositor layer, which consumes GPU memory. Use it sparingly and only on elements that actually animate. For elements that animate on load, set it in CSS. For hover animations, prefer setting it only during the interaction.

♿ Accessibility & Reduced Motion

Approximately 1 in 3 adults report motion sensitivity. Animations can trigger vertigo, nausea, or seizures in people with vestibular disorders. As developers, we have a responsibility to make motion optional.

prefers-reduced-motion

This media query detects whether the user has requested reduced motion in their OS settings (macOS: System Settings → Accessibility → Display → Reduce Motion):

CSS
/* Approach 1: Remove motion for those who prefer it reduced */
@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;
    }
}

/* Approach 2 (progressive): Only ADD motion for those who want it */
.card {
    /* No motion by default */
}

@media (prefers-reduced-motion: no-preference) {
    .card {
        transition: transform 0.3s ease;
    }
    .card:hover {
        transform: translateY(-4px);
    }
}
💡
Progressive Enhancement for Motion Approach 2 above is the most inclusive: no motion by default, animations only added inside prefers-reduced-motion: no-preference. This way, new or unknown environments get no animation, and only users who explicitly haven't opted out get the full experience.

Motion Accessibility Guidelines

📋
Best Practices:
  • Keep animations short — under 500ms for UI transitions, under 5s for decorative ones
  • Avoid large-scale movement (parallax scrolling, zoom effects) without a reduce-motion alternative
  • Never use flashing/strobing effects (more than 3 flashes per second can trigger seizures)
  • Provide a "pause" control for auto-playing animations (carousels, marquees)
  • Motion should be purposeful — guide attention, provide feedback, show relationships

🧩 Real-World Animation Patterns

Fade In on Scroll (CSS-only trigger concept)

CSS
Try it →
@keyframes fadeInUp {
    from {
        opacity: 0;
        transform: translateY(20px);
    }
    to {
        opacity: 1;
        transform: translateY(0);
    }
}

.reveal {
    animation: fadeInUp 0.6s ease both;
}

.reveal:nth-child(1) { animation-delay: 0s; }
.reveal:nth-child(2) { animation-delay: 0.15s; }
.reveal:nth-child(3) { animation-delay: 0.3s; }

Pulse / Notification Badge

CSS
Try it →
@keyframes pulse {
    0%, 100% {
        box-shadow: 0 0 0 0 rgba(239,68,68,0.5);
    }
    70% {
        box-shadow: 0 0 0 12px rgba(239,68,68,0);
    }
}

.badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 24px;
    height: 24px;
    background: #ef4444;
    color: white;
    border-radius: 50%;
    font-size: 0.75rem;
    font-weight: 700;
    animation: pulse 2s ease-in-out infinite;
}

Skeleton Loading

CSS
Try it →
@keyframes skeleton-wave {
    0% { background-position: -200% center; }
    100% { background-position: 200% center; }
}

.skeleton {
    background: linear-gradient(90deg,
        #e2e8f0 25%, #f1f5f9 50%, #e2e8f0 75%);
    background-size: 200% 100%;
    animation: skeleton-wave 1.5s ease-in-out infinite;
    border-radius: 6px;
}

.skeleton-avatar {
    width: 48px;
    height: 48px;
    border-radius: 50%;
}
.skeleton-line {
    height: 14px;
    margin-bottom: 8px;
}
.skeleton-line.short {
    width: 60%;
}

✏️ Exercises

Exercise 1: Smooth Button

Create a button that on hover: changes background color, lifts up (translateY), and gains a colored box-shadow. Use a single transition shorthand with different durations per property.

Exercise 2: Card Flip

Build a flip card with a front face (product image placeholder) and back face (product description). Use perspective, transform-style: preserve-3d, backface-visibility: hidden, and rotateY(180deg).

Exercise 3: Loading Spinner

Create a circular loading spinner using border, border-top-color, and @keyframes rotate { to { transform: rotate(360deg) } }. Make it spin infinitely with linear timing.

Exercise 4: Staggered List Reveal

Create a list of 5 items that fade in and slide up on page load with staggered animation-delay (0s, 0.1s, 0.2s, etc.). Use animation-fill-mode: both so items start invisible.

Exercise 5: Accessible Motion

Take any of the above exercises and add a @media (prefers-reduced-motion: reduce) block that removes or simplifies the animation. Test by enabling "Reduce motion" in your OS accessibility settings.

📋 Module Summary

🔄
CSS Transitions

Smooth A→B changes. Specify transition-property, duration, timing-function, and delay. Always name specific properties instead of all.

🔀
Transforms

translate(), rotate(), scale(), skew(). GPU-accelerated, don't affect layout. Chain multiple in one declaration (order matters).

🧊
3D Transforms

perspective on parent, transform-style: preserve-3d, backface-visibility: hidden for card flips and 3D effects.

🎬
@keyframes

Multi-step animations with full timing control. Define named keyframes, apply with animation shorthand. Use fill-mode: both and stagger with animation-delay.

⏱️
Timing Functions

ease, linear, ease-in-out, or custom cubic-bezier() for overshoot. steps() for frame-by-frame like typewriters.

Performance

Only animate transform and opacity for 60fps. Use will-change sparingly. Avoid animating layout properties like width/height.

Accessibility

Always implement prefers-reduced-motion. Keep animations purposeful and short. No flashing >3 times/second. Provide pause controls.

Next Module →

Module 11: Accessibility & SEO

Build for everyone. ARIA roles, keyboard navigation, color contrast, screen readers, meta tags, Open Graph, and structured data.