Polyglot
Languages Web (HTML / CSS / JS) css animations
Web (HTML / CSS / JS) § css-animations

Animations and responsive

CSS admits substantial animation and responsive design through several mechanisms: transitions (transition) admit substantial smooth state-change animations; keyframe animations (@keyframes + animation) admit substantial complex multi-stage animations; transforms (transform) admit translation, rotation, scaling, skewing of elements without affecting layout; media queries admit responsive design via viewport-size and device-feature checks; prefers-reduced-motion admits accessibility-aware animation. The combination — transitions for state changes, keyframe animations for complex motion, transforms for performant manipulation, media queries for responsive design — is the substance of CSS’s animation and adaptive-design surface.

Transitions

Animate property changes on state transitions:

.button {
    background: blue;
    transition: background 0.3s;                   /* property duration */
}

.button:hover {
    background: red;
}

The full transition syntax:

.element {
    transition: property duration timing-function delay;
    transition: background 0.3s ease-in-out 0.1s;

    /* Multiple properties: */
    transition: background 0.3s, color 0.2s, transform 0.5s;

    /* All properties: */
    transition: all 0.3s;                          /* avoid; performance burden */
}

The principal timing functions:

transition-timing-function: linear;
transition-timing-function: ease;                  /* default */
transition-timing-function: ease-in;
transition-timing-function: ease-out;
transition-timing-function: ease-in-out;
transition-timing-function: cubic-bezier(0.25, 0.1, 0.25, 1);
transition-timing-function: steps(5, end);         /* discrete steps */

The conventional discipline:

  • Specify which properties to transition — avoid all for performance.
  • Use cubic-bezier for substantial custom curves.
  • Use 0.2s0.4s for substantial conventional durations.

Keyframe animations

For complex multi-stage animations:

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

@keyframes slideIn {
    0% { transform: translateX(-100%); opacity: 0; }
    50% { opacity: 1; }
    100% { transform: translateX(0); opacity: 1; }
}

@keyframes pulse {
    0%, 100% { transform: scale(1); }
    50% { transform: scale(1.1); }
}

.element {
    animation: fadeIn 0.3s ease-in-out;

    /* Full syntax: */
    animation:
        name           /* keyframes name */
        1s             /* duration */
        ease-in-out    /* timing function */
        0.5s           /* delay */
        infinite       /* iteration count (number or 'infinite') */
        alternate      /* direction (normal, reverse, alternate, alternate-reverse) */
        forwards       /* fill mode (none, forwards, backwards, both) */
        running;       /* play state (running, paused) */
}

/* Or individual properties: */
.element {
    animation-name: fadeIn;
    animation-duration: 0.3s;
    animation-timing-function: ease-in-out;
    animation-delay: 0.5s;
    animation-iteration-count: infinite;
    animation-direction: alternate;
    animation-fill-mode: forwards;
    animation-play-state: running;
}

The principal fill modes:

  • none — element returns to original state after animation.
  • forwards — keeps the final state.
  • backwards — applies the first frame during delay.
  • both — both forwards and backwards.

Transforms

Transforms admit translation, rotation, scaling, skewing — without affecting layout:

.element {
    transform: translate(50px, 100px);             /* x, y */
    transform: translateX(50px);
    transform: translateY(100px);
    transform: translateZ(50px);                   /* requires perspective */

    transform: rotate(45deg);
    transform: rotateX(45deg);                     /* X axis */
    transform: rotateY(45deg);
    transform: rotateZ(45deg);

    transform: scale(1.5);                         /* both axes */
    transform: scale(1.5, 2);                      /* x, y */
    transform: scaleX(2);

    transform: skew(10deg);

    /* Multiple (applied right-to-left): */
    transform: translate(50px, 0) rotate(45deg) scale(1.2);

    /* Origin: */
    transform-origin: center;                      /* default */
    transform-origin: top left;
    transform-origin: 50% 50%;
    transform-origin: 0 100%;

    /* 3D (with perspective): */
    perspective: 1000px;                           /* on parent */
    transform-style: preserve-3d;
    transform: rotate3d(1, 1, 0, 45deg);
    transform: matrix3d(...);                      /* substantial */
}

The mechanism admits hardware-accelerated animation — substantial performance for transform and opacity over top, left, width, height.

Performance

The browser admits substantial optimisation for animating:

  • transform — admits GPU acceleration.
  • opacity — admits GPU acceleration.

Animating other properties (width, height, top, left, padding, etc.) admits layout/paint, substantial slower:

/* Slow: */
.element { transition: width 0.3s; }

.element:hover { width: 200px; }

/* Fast (using transform): */
.element { transition: transform 0.3s; transform-origin: left; }
.element:hover { transform: scaleX(2); }

The will-change hint admits substantial preparation:

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

The conventional discipline applies will-change temporarily (during interaction) and removes it after — substantial memory cost.

Common animation patterns

Fade in

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

.fade-in {
    animation: fadeIn 0.3s ease-out;
}

Slide in from left

@keyframes slideInLeft {
    from { transform: translateX(-100%); }
    to { transform: translateX(0); }
}

.slide-in-left {
    animation: slideInLeft 0.3s ease-out;
}

Fade in with movement

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

.fade-in-up {
    animation: fadeInUp 0.4s ease-out;
}

Pulse

@keyframes pulse {
    0%, 100% { transform: scale(1); }
    50% { transform: scale(1.05); }
}

.pulse {
    animation: pulse 2s ease-in-out infinite;
}

Spin

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

.spinner {
    animation: spin 1s linear infinite;
}

Shake

@keyframes shake {
    0%, 100% { transform: translateX(0); }
    20%, 60% { transform: translateX(-10px); }
    40%, 80% { transform: translateX(10px); }
}

.shake {
    animation: shake 0.5s;
}

Bounce

@keyframes bounce {
    0%, 100% { transform: translateY(0); }
    50% { transform: translateY(-30px); }
}

.bounce {
    animation: bounce 0.5s;
}

Hover effects

.button {
    transition: transform 0.2s, box-shadow 0.2s;
}

.button:hover {
    transform: translateY(-2px);
    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
}

.button:active {
    transform: translateY(0);
}

Loading spinner

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

Skeleton placeholder

@keyframes skeleton-pulse {
    0% { opacity: 1; }
    50% { opacity: 0.4; }
    100% { opacity: 1; }
}

.skeleton {
    background: #e0e0e0;
    border-radius: 4px;
    animation: skeleton-pulse 1.5s ease-in-out infinite;
}

Stagger animation

.list-item {
    opacity: 0;
    animation: fadeInUp 0.4s ease-out forwards;
}

.list-item:nth-child(1) { animation-delay: 0.0s; }
.list-item:nth-child(2) { animation-delay: 0.1s; }
.list-item:nth-child(3) { animation-delay: 0.2s; }
.list-item:nth-child(4) { animation-delay: 0.3s; }

Media queries

Responsive design via viewport conditions:

/* Mobile-first (preferred): */
.container {
    padding: 1rem;
}

@media (min-width: 768px) {
    .container {
        padding: 2rem;
    }
}

@media (min-width: 1200px) {
    .container {
        padding: 3rem;
        max-width: 1200px;
        margin: 0 auto;
    }
}

/* Multiple conditions: */
@media (min-width: 768px) and (max-width: 1199px) {
    /* tablet only */
}

@media (min-width: 768px), (orientation: landscape) {
    /* either */
}

/* Negation: */
@media not (min-width: 768px) {
    /* below 768 */
}

Media features

The principal media features:

/* Viewport size: */
@media (min-width: 768px) { ... }
@media (max-width: 1199px) { ... }
@media (width: 1024px) { ... }                    /* exact */

/* Aspect ratio: */
@media (aspect-ratio: 16/9) { ... }
@media (min-aspect-ratio: 16/9) { ... }

/* Orientation: */
@media (orientation: landscape) { ... }
@media (orientation: portrait) { ... }

/* Resolution: */
@media (min-resolution: 2dppx) { ... }             /* high-DPI displays */

/* Color scheme: */
@media (prefers-color-scheme: dark) { ... }
@media (prefers-color-scheme: light) { ... }

/* Motion preferences: */
@media (prefers-reduced-motion: reduce) { ... }
@media (prefers-reduced-motion: no-preference) { ... }

/* Contrast preferences: */
@media (prefers-contrast: more) { ... }
@media (prefers-contrast: less) { ... }

/* Print: */
@media print { ... }
@media screen { ... }

/* Pointer: */
@media (pointer: coarse) { ... }                   /* touch */
@media (pointer: fine) { ... }                     /* mouse */

/* Hover capability: */
@media (hover: hover) { ... }                      /* admits hover */
@media (hover: none) { ... }                       /* touch-only */

Common responsive patterns

Mobile-first

/* Base styles for mobile */
.container {
    padding: 1rem;
}

.grid {
    display: grid;
    grid-template-columns: 1fr;
    gap: 1rem;
}

/* Tablet */
@media (min-width: 768px) {
    .container {
        padding: 2rem;
    }
    .grid {
        grid-template-columns: 1fr 1fr;
    }
}

/* Desktop */
@media (min-width: 1024px) {
    .grid {
        grid-template-columns: repeat(3, 1fr);
        gap: 1.5rem;
    }
}

Dark mode

:root {
    --bg: white;
    --text: black;
    --accent: blue;
}

@media (prefers-color-scheme: dark) {
    :root {
        --bg: #1a1a1a;
        --text: #e0e0e0;
        --accent: #6cf;
    }
}

body {
    background: var(--bg);
    color: var(--text);
}

Reduced motion

.element {
    animation: slideIn 0.3s ease-out;
    transition: transform 0.2s;
}

@media (prefers-reduced-motion: reduce) {
    .element {
        animation: none;
        transition: none;
    }
}

/* Or globally: */
@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;
    }
}

The mechanism admits substantial accessibility — users with vestibular disorders or substantial motion sensitivity.

@media print {
    nav, footer, .no-print {
        display: none;
    }

    body {
        font-size: 11pt;
        color: black;
        background: white;
    }

    a::after {
        content: " (" attr(href) ")";
    }

    .page-break {
        page-break-after: always;
    }
}

High-DPI

.icon {
    background: url("icon.png");
}

@media (min-resolution: 2dppx) {
    .icon {
        background: url("icon@2x.png");
        background-size: 24px 24px;
    }
}

Hover capability

.card {
    /* base styles */
}

@media (hover: hover) {
    .card:hover {
        transform: translateY(-2px);
        box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
    }
}

@media (hover: none) {
    /* touch-only adjustments */
    .card {
        padding: 1.5rem;                           /* larger tap targets */
    }
}

Container queries

For component-level responsive design:

.card {
    container-type: inline-size;
    container-name: card;
}

@container card (min-width: 400px) {
    .card__title {
        font-size: 1.5rem;
    }
}

@container card (min-width: 600px) {
    .card {
        display: grid;
        grid-template-columns: 1fr 2fr;
    }
}

The mechanism admits substantial component-driven responsiveness — substantial improvement over media-query-only design.

CSS custom properties for animation

:root {
    --transition-fast: 0.15s;
    --transition-base: 0.3s;
    --transition-slow: 0.5s;
    --easing-default: cubic-bezier(0.25, 0.1, 0.25, 1);
}

.button {
    transition: background var(--transition-fast) var(--easing-default);
}

.modal {
    transition: opacity var(--transition-base) var(--easing-default);
}

The mechanism admits substantial design-system consistency.

Animation event hooks (JavaScript)

const element = document.querySelector(".animated");

element.addEventListener("animationstart", (e) => {
    console.log(`animation ${e.animationName} started`);
});

element.addEventListener("animationend", (e) => {
    console.log(`animation ${e.animationName} ended`);
});

element.addEventListener("animationiteration", (e) => {
    console.log(`animation iteration`);
});

element.addEventListener("transitionend", (e) => {
    console.log(`transition on ${e.propertyName} ended`);
});

Common patterns

View transition (modern)

::view-transition-old(root),
::view-transition-new(root) {
    animation-duration: 0.3s;
}
async function navigate(url) {
    if (!document.startViewTransition) {
        location.href = url;
        return;
    }

    document.startViewTransition(async () => {
        // update DOM
        await updateContent(url);
    });
}

The View Transitions API admits substantial substantial cross-page animations.

Smooth scrolling

html {
    scroll-behavior: smooth;
}

@media (prefers-reduced-motion: reduce) {
    html {
        scroll-behavior: auto;
    }
}

Scroll-triggered animation

.fade-in {
    opacity: 0;
    transform: translateY(20px);
    transition: opacity 0.6s, transform 0.6s;
}

.fade-in.visible {
    opacity: 1;
    transform: translateY(0);
}
const observer = new IntersectionObserver((entries) => {
    entries.forEach((entry) => {
        if (entry.isIntersecting) {
            entry.target.classList.add("visible");
        }
    });
}, { threshold: 0.1 });

document.querySelectorAll(".fade-in").forEach((el) => observer.observe(el));

Parallax (modern)

CSS scroll-timeline admits substantial scroll-driven animations:

@keyframes slide-up {
    from { transform: translateY(100px); opacity: 0; }
    to { transform: translateY(0); opacity: 1; }
}

.element {
    animation: slide-up linear;
    animation-timeline: view();
    animation-range: entry 0% cover 50%;
}

The mechanism admits substantial scroll-tied animation declaratively.

Theme transition

* {
    transition: background-color 0.3s, color 0.3s;
}

The transition admits substantial theme-switch smoothness — applied to all elements when switching dark/light mode.

.modal-backdrop {
    background: rgba(0, 0, 0, 0);
    transition: background 0.3s;
}

.modal-backdrop.visible {
    background: rgba(0, 0, 0, 0.5);
}

.modal {
    transform: translateY(20px) scale(0.95);
    opacity: 0;
    transition: transform 0.3s ease-out, opacity 0.3s ease-out;
}

.modal-backdrop.visible .modal {
    transform: translateY(0) scale(1);
    opacity: 1;
}

Fluid typography

:root {
    --font-size-base: clamp(1rem, 2vw, 1.25rem);
    --font-size-h1: clamp(2rem, 5vw, 4rem);
}

body { font-size: var(--font-size-base); }
h1 { font-size: var(--font-size-h1); }

The clamp(min, ideal, max) admits substantial fluid sizing.

A note on the conventional discipline

The contemporary CSS animation/responsive advice:

  • Use transitions for state changes; animations for complex motion.
  • Animate transform and opacity — substantial GPU acceleration.
  • Avoid animating layout properties — substantial paint cost.
  • Use will-change sparingly — only during interaction.
  • Respect prefers-reduced-motion — substantial accessibility.
  • Design mobile-first — base styles for small screens, expand up.
  • Use prefers-color-scheme for dark mode.
  • Use container queries for component-level responsiveness.
  • Use clamp() for fluid sizing.
  • Use the View Transitions API for substantial cross-page animations.
  • Test with reduced motion — substantial accessibility verification.

The combination — transitions for state changes, keyframe animations for complex motion, transforms for performant manipulation, media queries for viewport-based responsive design, container queries for component-level responsiveness, the substantial accessibility integration via prefers-reduced-motion — is the substance of CSS’s animation and adaptive-design surface. The discipline produces substantial smooth, accessible, performant, responsive interfaces.