Lesson 13: CSS Animations & Transitions

Learning Outcomes

By the end of this lesson, you will be able to:

Why CSS Animations Matter

Animations are more than just visual flair—they improve user experience by providing feedback, guiding attention, and making interfaces feel responsive and alive.

Benefits of CSS Animations:

When to Use Animations:

CSS Transitions

Transitions allow you to smoothly animate changes between CSS property values. When a property changes (like on hover), the transition interpolates between the old and new values.

Basic Transition Syntax

/* Transition a specific property */
.button {
    background-color: blue;
    transition: background-color 0.3s ease;
}

.button:hover {
    background-color: green;
}

/* Transition all properties */
.card {
    transition: all 0.3s ease;
}

/* Transition multiple properties with different timings */
.box {
    transition: 
        transform 0.3s cubic-bezier(0.68, -0.55, 0.265, 1.55),
        opacity 0.2s ease,
        background-color 0.5s linear;
}

Transition Properties

.element {
    /* Shorthand: property duration timing-function delay */
    transition: transform 0.3s ease-in-out 0.1s;
    
    /* Or use individual properties */
    transition-property: transform;
    transition-duration: 0.3s;
    transition-timing-function: ease-in-out;
    transition-delay: 0.1s;
}

Example: Button with Transition

Timing Functions and Easing

Timing functions control the pace of an animation. They determine how intermediate values are calculated during the transition.

Built-in Timing Functions

/* Linear - constant speed */
transition: all 0.3s linear;

/* Ease - slow start, fast middle, slow end (default) */
transition: all 0.3s ease;

/* Ease-in - slow start, fast end */
transition: all 0.3s ease-in;

/* Ease-out - fast start, slow end */
transition: all 0.3s ease-out;

/* Ease-in-out - slow start and end */
transition: all 0.3s ease-in-out;

Custom Cubic Bezier

/* Create custom easing curves */
.bounce {
    transition: transform 0.5s cubic-bezier(0.68, -0.55, 0.265, 1.55);
}

.smooth-slide {
    transition: transform 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94);
}

/* Use https://cubic-bezier.com to create custom curves */

Step Timing Function

/* Jump in discrete steps (useful for sprite animations) */
.sprite {
    transition: background-position 0.8s steps(8);
}

2D Transforms

Transforms allow you to move, rotate, scale, and skew elements in 2D space. Transforms are performant because they don't trigger layout recalculation.

Transform Functions

/* Translate - move element */
transform: translate(50px, 100px);  /* X, Y */
transform: translateX(50px);
transform: translateY(100px);

/* Scale - resize element */
transform: scale(1.5);       /* Uniform scaling */
transform: scale(2, 0.5);    /* X, Y */
transform: scaleX(2);
transform: scaleY(0.5);

/* Rotate - rotate element */
transform: rotate(45deg);
transform: rotate(-30deg);

/* Skew - slant element */
transform: skew(20deg, 10deg);  /* X, Y */
transform: skewX(20deg);
transform: skewY(10deg);

Combining Transforms

/* Multiple transforms are applied right to left */
.element {
    /* Rotate, then translate, then scale */
    transform: scale(1.2) translate(50px, 0) rotate(15deg);
}

/* Transform origin - change the pivot point */
.element {
    transform-origin: top left;  /* Default is center */
    transform: rotate(45deg);
}

Example: Hover Card with Transform

Hover me!

I scale and lift on hover

Keyframe Animations

Keyframe animations allow you to create complex, multi-stage animations with precise control over each stage. Unlike transitions, animations can run automatically without a trigger.

Defining Keyframes

/* Using from/to keywords */
@keyframes fadeIn {
    from {
        opacity: 0;
    }
    to {
        opacity: 1;
    }
}

/* Using percentages for multi-stage animation */
@keyframes bounce {
    0% {
        transform: translateY(0);
    }
    50% {
        transform: translateY(-20px);
    }
    100% {
        transform: translateY(0);
    }
}

/* Complex animation with multiple properties */
@keyframes slideInBounce {
    0% {
        opacity: 0;
        transform: translateX(-100px) scale(0.8);
    }
    60% {
        transform: translateX(10px) scale(1.1);
    }
    100% {
        opacity: 1;
        transform: translateX(0) scale(1);
    }
}

Applying Animations

/* Shorthand syntax */
.element {
    animation: fadeIn 0.5s ease-in-out 0.2s 1 normal forwards;
    /* name | duration | timing | delay | iteration | direction | fill-mode */
}

/* Individual properties */
.element {
    animation-name: slideInBounce;
    animation-duration: 0.8s;
    animation-timing-function: cubic-bezier(0.68, -0.55, 0.265, 1.55);
    animation-delay: 0s;
    animation-iteration-count: 1;      /* or 'infinite' */
    animation-direction: normal;       /* or 'reverse', 'alternate', 'alternate-reverse' */
    animation-fill-mode: forwards;     /* or 'backwards', 'both', 'none' */
    animation-play-state: running;     /* or 'paused' */
}

Example: Pulsing Animation

I'm pulsing!

Animation Properties

Fill Mode

Controls what happens before and after the animation runs:

/* none - no styles applied outside animation */
animation-fill-mode: none;

/* forwards - retain final keyframe styles */
animation-fill-mode: forwards;

/* backwards - apply initial keyframe styles during delay */
animation-fill-mode: backwards;

/* both - apply both forwards and backwards */
animation-fill-mode: both;

Direction

/* normal - play forward */
animation-direction: normal;

/* reverse - play backward */
animation-direction: reverse;

/* alternate - forward then backward */
animation-direction: alternate;

/* alternate-reverse - backward then forward */
animation-direction: alternate-reverse;

Iteration Count

/* Run once */
animation-iteration-count: 1;

/* Run 3 times */
animation-iteration-count: 3;

/* Run forever */
animation-iteration-count: infinite;

3D Transforms and Perspective

CSS 3D transforms allow you to create depth and dimension. Combined with perspective, you can create truly three-dimensional effects.

Perspective

/* Perspective on parent creates shared 3D space */
.scene {
    perspective: 1000px;  /* Lower = more extreme perspective */
}

/* Perspective on element itself */
.element {
    transform: perspective(1000px) rotateY(45deg);
}

3D Transform Functions

/* 3D Translation */
transform: translate3d(50px, 100px, -200px);
transform: translateZ(-200px);

/* 3D Rotation */
transform: rotateX(45deg);   /* Rotate around X axis */
transform: rotateY(45deg);   /* Rotate around Y axis */
transform: rotateZ(45deg);   /* Same as rotate() */
transform: rotate3d(1, 1, 0, 45deg);  /* Custom axis */

/* 3D Scale */
transform: scale3d(1.5, 1.5, 2);
transform: scaleZ(2);

Transform Style

/* Preserve 3D - children maintain 3D positioning */
.parent {
    transform-style: preserve-3d;
}

/* Flat (default) - children are flattened to parent plane */
.parent {
    transform-style: flat;
}

Backface Visibility

/* Hide element when rotated away from viewer */
.card-face {
    backface-visibility: hidden;
}

/* Show backface (default) */
.card-face {
    backface-visibility: visible;
}

Example: 3D Flip Card

Front
Back!

Hover to flip

The Checkbox Hack

The "checkbox hack" is a technique that uses hidden checkboxes combined with the :checked pseudo-class to create interactive components without JavaScript.

How It Works

<input type="checkbox" id="toggle" class="toggle-checkbox">
<label for="toggle" class="toggle-label">Click to toggle</label>
<div class="toggle-content">
    This content is toggled!
</div>
/* Hide the checkbox */
.toggle-checkbox {
    display: none;
}

/* Style the label as a button */
.toggle-label {
    display: inline-block;
    padding: 10px 20px;
    background: #3b82f6;
    color: white;
    cursor: pointer;
    border-radius: 4px;
}

/* Hide content by default */
.toggle-content {
    max-height: 0;
    overflow: hidden;
    transition: max-height 0.3s ease;
}

/* Show content when checkbox is checked */
.toggle-checkbox:checked ~ .toggle-content {
    max-height: 500px;  /* Large enough for content */
}

/* Change label when checked */
.toggle-checkbox:checked ~ .toggle-label {
    background: #10b981;
}

Example: Accordion with Checkbox Hack

The checkbox hack uses the :checked pseudo-class to toggle content visibility without JavaScript. Click the label to toggle!

The :target Pseudo-Class

The :target pseudo-class selects an element when the URL's fragment identifier (hash) matches that element's ID. This enables pure CSS modals, tabs, and navigation.

Basic Usage

<a href="#section1">Go to Section 1</a>
<a href="#section2">Go to Section 2</a>

<div id="section1">Section 1 Content</div>
<div id="section2">Section 2 Content</div>
/* Hide all sections by default */
div[id^="section"] {
    display: none;
}

/* Show the targeted section */
div[id^="section"]:target {
    display: block;
    animation: fadeIn 0.3s ease;
}

@keyframes fadeIn {
    from { opacity: 0; }
    to { opacity: 1; }
}

Pure CSS Modal

/* Hide modal by default */
.modal {
    display: none;
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background: rgba(0, 0, 0, 0.8);
    align-items: center;
    justify-content: center;
}

/* Show modal when targeted */
.modal:target {
    display: flex;
    animation: fadeIn 0.3s ease;
}

Animation Performance

Not all CSS properties are created equal when it comes to animation performance. Some properties trigger expensive layout and paint operations.

Performance Tiers

Optimization Tips

/* ❌ Bad - animates layout-triggering properties */
.slow {
    transition: width 0.3s, height 0.3s, left 0.3s;
}

/* ✅ Good - uses transform instead */
.fast {
    transition: transform 0.3s, opacity 0.3s;
}

/* Use will-change to hint browser optimization */
.animated {
    will-change: transform, opacity;
}

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

Prefer Transform Over Position

/* ❌ Slow - changes layout */
@keyframes slideInSlow {
    from { left: -100px; }
    to { left: 0; }
}

/* ✅ Fast - uses transform */
@keyframes slideInFast {
    from { transform: translateX(-100px); }
    to { transform: translateX(0); }
}

Respect User Preferences

/* Reduce or disable animations for users who prefer reduced motion */
@media (prefers-reduced-motion: reduce) {
    * {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
    }
}

Worked Example 1: Accordion Component

Let's build a complete accordion component using the checkbox hack.

<div class="accordion">
    <div class="accordion-item">
        <input type="checkbox" id="item1" class="accordion-toggle">
        <label for="item1" class="accordion-header">
            What is CSS?
            <span class="icon">▼</span>
        </label>
        <div class="accordion-content">
            <p>CSS (Cascading Style Sheets) is the language used to style HTML documents.</p>
        </div>
    </div>
</div>
.accordion-item {
    border: 1px solid #e5e7eb;
    border-radius: 6px;
    margin-bottom: 10px;
    overflow: hidden;
}

.accordion-toggle {
    display: none;
}

.accordion-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 15px;
    background: #f9fafb;
    cursor: pointer;
    user-select: none;
    transition: background 0.2s;
}

.accordion-header:hover {
    background: #f3f4f6;
}

.accordion-icon {
    transition: transform 0.3s ease;
}

.accordion-content {
    max-height: 0;
    overflow: hidden;
    transition: max-height 0.4s ease;
}

.accordion-toggle:checked ~ .accordion-header {
    background: #eff6ff;
}

.accordion-toggle:checked ~ .accordion-header .icon {
    transform: rotate(180deg);
}

.accordion-toggle:checked ~ .accordion-content {
    max-height: 500px;
}

Worked Example 2: Modal Dialog

Building a modal using the :target pseudo-class.

<a href="#myModal" class="open-modal">Open Modal</a>

<div id="myModal" class="modal">
    <div class="modal-content">
        <a href="#" class="modal-close">×</a>
        <h2>Modal Title</h2>
        <p>Modal content goes here.</p>
    </div>
</div>
.modal {
    display: none;
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background: rgba(0, 0, 0, 0.8);
    align-items: center;
    justify-content: center;
    z-index: 1000;
}

.modal:target {
    display: flex;
    animation: fadeIn 0.3s ease;
}

.modal-content {
    background: white;
    padding: 30px;
    border-radius: 8px;
    max-width: 500px;
    width: 90%;
    position: relative;
    animation: slideDown 0.3s ease;
}

.modal-close {
    position: absolute;
    top: 10px;
    right: 15px;
    font-size: 28px;
    color: #666;
    text-decoration: none;
    transition: color 0.2s;
}

.modal-close:hover {
    color: #000;
}

@keyframes fadeIn {
    from { opacity: 0; }
    to { opacity: 1; }
}

@keyframes slideDown {
    from { transform: translateY(-50px); opacity: 0; }
    to { transform: translateY(0); opacity: 1; }
}

Worked Example 3: 3D Rotating Cube

Creating a 3D cube with CSS transforms.

<div class="scene">
    <div class="cube">
        <div class="cube-face front">Front</div>
        <div class="cube-face back">Back</div>
        <div class="cube-face right">Right</div>
        <div class="cube-face left">Left</div>
        <div class="cube-face top">Top</div>
        <div class="cube-face bottom">Bottom</div>
    </div>
</div>
.scene {
    width: 200px;
    height: 200px;
    perspective: 1000px;
}

.cube {
    width: 100%;
    height: 100%;
    position: relative;
    transform-style: preserve-3d;
    animation: rotateCube 10s linear infinite;
}

.cube-face {
    position: absolute;
    width: 200px;
    height: 200px;
    border: 2px solid white;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 24px;
    font-weight: bold;
    opacity: 0.9;
}

.front  { background: rgba(255, 0, 0, 0.7); transform: rotateY(0deg) translateZ(100px); }
.back   { background: rgba(0, 255, 0, 0.7); transform: rotateY(180deg) translateZ(100px); }
.right  { background: rgba(0, 0, 255, 0.7); transform: rotateY(90deg) translateZ(100px); }
.left   { background: rgba(255, 255, 0, 0.7); transform: rotateY(-90deg) translateZ(100px); }
.top    { background: rgba(255, 0, 255, 0.7); transform: rotateX(90deg) translateZ(100px); }
.bottom { background: rgba(0, 255, 255, 0.7); transform: rotateX(-90deg) translateZ(100px); }

@keyframes rotateCube {
    from { transform: rotateX(0) rotateY(0); }
    to { transform: rotateX(360deg) rotateY(360deg); }
}

Worked Example 4: Loading Spinners

Creating various loading spinner animations.

/* Simple spinner */
.spinner {
    width: 40px;
    height: 40px;
    border: 4px solid #f3f3f3;
    border-top: 4px solid #3b82f6;
    border-radius: 50%;
    animation: spin 1s linear infinite;
}

@keyframes spin {
    0% { transform: rotate(0deg); }
    100% { transform: rotate(360deg); }
}

/* Pulse spinner */
.pulse-spinner {
    width: 40px;
    height: 40px;
    background: #3b82f6;
    border-radius: 50%;
    animation: pulse 1.5s ease-in-out infinite;
}

@keyframes pulse {
    0%, 100% {
        transform: scale(0.8);
        opacity: 1;
    }
    50% {
        transform: scale(1.2);
        opacity: 0.5;
    }
}

/* Bouncing dots */
.dots-spinner {
    display: flex;
    gap: 8px;
}

.dot {
    width: 12px;
    height: 12px;
    background: #3b82f6;
    border-radius: 50%;
    animation: bounce 1.4s ease-in-out infinite;
}

.dot:nth-child(1) { animation-delay: 0s; }
.dot:nth-child(2) { animation-delay: 0.2s; }
.dot:nth-child(3) { animation-delay: 0.4s; }

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

Spinner Examples

Practice Opportunity

Challenge: Build an Animated Photo Gallery

Create a photo gallery with the following features:

  1. Grid of images that scale and lift on hover
  2. Clicking an image opens a modal (using :target)
  3. Modal slides in with animation
  4. Add prev/next navigation with transitions
  5. Include loading spinners

Bonus Challenges:

Summary

In this lesson, you learned:

CSS animations and transforms are powerful tools for creating engaging user experiences. By understanding performance implications and respecting user preferences, you can create delightful interactions that enhance your websites.