CSS Animations and Transitions

1. CSS Transition Properties and Timing

Property Values Description Default
transition-property all | none | property-name Which properties to transition all
transition-duration time (s, ms) How long transition takes 0s
transition-timing-function ease | linear | ease-in | ease-out | ease-in-out | cubic-bezier() Speed curve of transition ease
transition-delay time (s, ms) Wait before starting transition 0s
transition property duration timing delay Shorthand for all transition properties all 0s ease 0s
Timing Function Curve Description Use Case
linear Constant speed Same speed start to finish Progress bars, mechanical
ease Slow-fast-slow Accelerate then decelerate General purpose (default)
ease-in Slow start Accelerate from start Elements leaving screen
ease-out Slow end Decelerate to stop Elements entering screen
ease-in-out Slow-fast-slow Smooth acceleration/deceleration Smooth movement
cubic-bezier() Custom curve Full control over acceleration Custom easing effects
steps() Stepped animation Jump between states Sprite animations, typewriter

Example: Basic transitions

/* Simple hover transition */
.button {
    background-color: blue;
    transition: background-color 0.3s ease;
}

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

/* Multiple properties */
.card {
    transform: scale(1);
    opacity: 1;
    transition: transform 0.3s ease, opacity 0.3s ease;
}

.card:hover {
    transform: scale(1.05);
    opacity: 0.9;
}

/* Shorthand - all properties */
.box {
    transition: all 0.3s ease;
}

/* Different timings for different properties */
.element {
    transition: 
        transform 0.3s ease-out,
        opacity 0.5s ease-in,
        background-color 0.2s linear;
}

/* Transition with delay */
.delayed {
    transition: opacity 0.5s ease 0.2s;
    /* opacity, 0.5s duration, ease timing, 0.2s delay */
}

/* Specific properties only */
.specific {
    transition-property: transform, opacity;
    transition-duration: 0.3s;
    transition-timing-function: ease-out;
}

Example: Advanced transition patterns

/* Staggered transitions */
.menu-item {
    opacity: 0;
    transform: translateX(-20px);
    transition: opacity 0.3s ease, transform 0.3s ease;
}

.menu-item:nth-child(1) { transition-delay: 0.1s; }
.menu-item:nth-child(2) { transition-delay: 0.2s; }
.menu-item:nth-child(3) { transition-delay: 0.3s; }

.menu.open .menu-item {
    opacity: 1;
    transform: translateX(0);
}

/* Bi-directional transitions (different in/out) */
.modal {
    opacity: 0;
    transform: scale(0.8);
    transition: opacity 0.3s ease-out, transform 0.3s ease-out;
}

.modal.open {
    opacity: 1;
    transform: scale(1);
    /* Fast opening: ease-out */
    transition: opacity 0.2s ease-out, transform 0.2s ease-out;
}

/* When closing, slower with ease-in */
.modal.closing {
    transition: opacity 0.4s ease-in, transform 0.4s ease-in;
}

/* Custom cubic-bezier easing */
.bouncy {
    transition: transform 0.5s cubic-bezier(0.68, -0.55, 0.265, 1.55);
    /* Bouncy overshoot effect */
}

/* Material Design easing */
.material-standard {
    transition: all 0.3s cubic-bezier(0.4, 0.0, 0.2, 1);
}

.material-decelerate {
    transition: all 0.3s cubic-bezier(0.0, 0.0, 0.2, 1);
}

.material-accelerate {
    transition: all 0.3s cubic-bezier(0.4, 0.0, 1, 1);
}

/* Stepped animation (sprite sheet) */
.sprite {
    background: url('sprite.png');
    width: 50px;
    height: 50px;
    transition: background-position 1s steps(10);
    /* 10 frames */
}

.sprite:hover {
    background-position: -500px 0;  /* 10 frames * 50px */
}
Note: Only animatable properties can transition (transform, opacity, colors, etc.). Avoid transition: all in production (performance). Use specific properties instead.

2. CSS Animation and Keyframe Syntax

Property Values Description Default
animation-name keyframe-name Name of @keyframes to use none
animation-duration time (s, ms) How long animation takes 0s
animation-timing-function ease | linear | ease-in | ease-out | ease-in-out | cubic-bezier() Speed curve of animation ease
animation-delay time (s, ms) Wait before starting 0s
animation-iteration-count number | infinite How many times to play 1
animation-direction normal | reverse | alternate | alternate-reverse Play direction normal
animation-fill-mode none | forwards | backwards | both Styles before/after animation none
animation-play-state running | paused Pause or resume animation running
animation name duration timing delay count direction fill Shorthand for all animation properties -
Keyframe Syntax Description Example
@keyframes Define animation sequence @keyframes slide { ... }
from/to Simple 2-state animation from { } to { }
Percentage Multi-state with percentages 0% { } 50% { } 100% { }
Multiple selectors Same styles at different points 0%, 100% { }

Example: Basic keyframe animations

/* Simple fade in */
@keyframes fadeIn {
    from {
        opacity: 0;
    }
    to {
        opacity: 1;
    }
}

.fade-element {
    animation: fadeIn 1s ease;
}

/* Multi-step animation with percentages */
@keyframes bounce {
    0% {
        transform: translateY(0);
    }
    25% {
        transform: translateY(-30px);
    }
    50% {
        transform: translateY(0);
    }
    75% {
        transform: translateY(-15px);
    }
    100% {
        transform: translateY(0);
    }
}

.bounce-element {
    animation: bounce 1s ease;
}

/* Infinite spinning loader */
@keyframes spin {
    from {
        transform: rotate(0deg);
    }
    to {
        transform: rotate(360deg);
    }
}

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

/* Pulse animation */
@keyframes pulse {
    0%, 100% {
        transform: scale(1);
        opacity: 1;
    }
    50% {
        transform: scale(1.1);
        opacity: 0.8;
    }
}

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

/* Slide in from left */
@keyframes slideInLeft {
    from {
        transform: translateX(-100%);
        opacity: 0;
    }
    to {
        transform: translateX(0);
        opacity: 1;
    }
}

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

Example: Advanced animation patterns

/* Animation with fill-mode */
@keyframes slideUp {
    from {
        transform: translateY(100%);
        opacity: 0;
    }
    to {
        transform: translateY(0);
        opacity: 1;
    }
}

.slide-up {
    /* Keep final state after animation */
    animation: slideUp 0.5s ease-out forwards;
}

/* Alternating direction */
@keyframes sway {
    0%, 100% {
        transform: rotate(-5deg);
    }
    50% {
        transform: rotate(5deg);
    }
}

.swaying {
    animation: sway 3s ease-in-out infinite alternate;
    /* Plays forward then backward */
}

/* Multiple animations */
.multi-animated {
    animation: 
        fadeIn 1s ease-out,
        slideUp 1s ease-out,
        pulse 2s ease-in-out 1s infinite;
    /* Third animation starts after 1s delay */
}

/* Paused animation (controlled by JS or hover) */
@keyframes scroll {
    from {
        transform: translateX(0);
    }
    to {
        transform: translateX(-100%);
    }
}

.scrolling-text {
    animation: scroll 10s linear infinite;
}

.scrolling-text:hover {
    animation-play-state: paused;
}

/* Complex multi-property animation */
@keyframes complexEntry {
    0% {
        transform: translateY(50px) scale(0.8) rotate(-5deg);
        opacity: 0;
        filter: blur(10px);
    }
    50% {
        transform: translateY(-10px) scale(1.05) rotate(2deg);
        filter: blur(0px);
    }
    100% {
        transform: translateY(0) scale(1) rotate(0deg);
        opacity: 1;
        filter: blur(0px);
    }
}

.complex-entry {
    animation: complexEntry 0.8s cubic-bezier(0.34, 1.56, 0.64, 1);
}

/* Typing effect with steps */
@keyframes typing {
    from {
        width: 0;
    }
    to {
        width: 100%;
    }
}

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

.typewriter {
    overflow: hidden;
    border-right: 2px solid;
    white-space: nowrap;
    animation: 
        typing 3.5s steps(40) 1s forwards,
        blink 0.75s step-end infinite;
}
Note: Use animation-fill-mode: forwards to keep final state. infinite loops should be performance-optimized. Use @keyframes for complex, multi-step animations.

3. Animation Timing Functions and Easing

Easing Function cubic-bezier() Equivalent Characteristic Best For
linear cubic-bezier(0, 0, 1, 1) Constant speed Mechanical, loading bars
ease cubic-bezier(0.25, 0.1, 0.25, 1) Slow start, fast middle, slow end General purpose
ease-in cubic-bezier(0.42, 0, 1, 1) Slow start, fast end Exits, dismissals
ease-out cubic-bezier(0, 0, 0.58, 1) Fast start, slow end Entrances, reveals
ease-in-out cubic-bezier(0.42, 0, 0.58, 1) Slow start and end Smooth loops
Custom Easing Values Effect Use Case
Bounce cubic-bezier(0.68, -0.55, 0.265, 1.55) Overshoots and bounces back Playful interactions
Elastic cubic-bezier(0.68, -0.75, 0.265, 1.75) More extreme bounce Attention-grabbing
Back cubic-bezier(0.6, -0.28, 0.735, 0.045) Pull back before going forward Anticipation effect
Material Standard cubic-bezier(0.4, 0.0, 0.2, 1) Material Design standard Android-style apps
iOS Swift Out cubic-bezier(0.4, 0.0, 0, 1) iOS-style deceleration iOS-style apps
steps() steps(n, start|end) Jump between n discrete states Sprite animations, flip counters

Example: Custom easing functions

/* Bouncy button press */
.bouncy-button {
    transition: transform 0.3s cubic-bezier(0.68, -0.55, 0.265, 1.55);
}

.bouncy-button:active {
    transform: scale(0.9);
}

/* Material Design motion */
.material-card {
    transition: all 0.3s cubic-bezier(0.4, 0.0, 0.2, 1);
}

/* Elastic menu */
@keyframes elasticSlide {
    0% {
        transform: translateX(-100%);
    }
    60% {
        transform: translateX(10%);
    }
    75% {
        transform: translateX(-5%);
    }
    90% {
        transform: translateX(2%);
    }
    100% {
        transform: translateX(0);
    }
}

.elastic-menu {
    animation: elasticSlide 0.8s;
}

/* Or with cubic-bezier */
.elastic-simple {
    animation: slideIn 0.8s cubic-bezier(0.68, -0.75, 0.265, 1.75);
}

/* Stepped animation for sprite */
@keyframes walk {
    to {
        background-position: -480px;  /* 8 frames * 60px */
    }
}

.character-walk {
    width: 60px;
    height: 60px;
    background: url('walk-sprite.png');
    animation: walk 0.8s steps(8) infinite;
}

/* Counter flip animation */
@keyframes flip {
    to {
        transform: translateY(-100%);
    }
}

.flip-counter {
    animation: flip 0.5s steps(10);
}

/* Smooth natural easing (iOS-style) */
.ios-sheet {
    transition: transform 0.5s cubic-bezier(0.32, 0.72, 0, 1);
}

/* Anticipation effect (pull back before forward) */
@keyframes anticipate {
    0% {
        transform: translateX(0);
    }
    20% {
        transform: translateX(-20px);
    }
    100% {
        transform: translateX(100%);
    }
}

.anticipate-exit {
    animation: anticipate 0.6s cubic-bezier(0.6, -0.28, 0.735, 0.045);
}

Example: Easing comparison and best practices

/* Entry animations: use ease-out (fast start, slow end) */
.enter {
    animation: fadeIn 0.3s ease-out;
}

/* Exit animations: use ease-in (slow start, fast end) */
.exit {
    animation: fadeOut 0.3s ease-in;
}

/* Movement: use ease-in-out */
.move {
    transition: transform 0.5s ease-in-out;
}

/* Quick feedback: use shorter durations */
.button-press {
    transition: transform 0.1s ease-out;
}

/* Attention-grabbing: use bounce */
.notification {
    animation: bounce 0.6s cubic-bezier(0.68, -0.55, 0.265, 1.55);
}

/* Smooth infinite loop: use ease-in-out */
@keyframes float {
    0%, 100% {
        transform: translateY(0);
    }
    50% {
        transform: translateY(-20px);
    }
}

.floating {
    animation: float 3s ease-in-out infinite;
}

/* Custom spring animation */
.spring {
    animation: springIn 0.6s cubic-bezier(0.175, 0.885, 0.32, 1.275);
}

/* Stepped loading animation */
@keyframes loadingSteps {
    to {
        background-position: -200px 0;
    }
}

.loading-bar {
    animation: loadingSteps 1s steps(10) infinite;
}

/* Easing visualizer helper classes */
.easing-linear { animation-timing-function: linear; }
.easing-ease { animation-timing-function: ease; }
.easing-ease-in { animation-timing-function: ease-in; }
.easing-ease-out { animation-timing-function: ease-out; }
.easing-ease-in-out { animation-timing-function: ease-in-out; }
.easing-bounce { animation-timing-function: cubic-bezier(0.68, -0.55, 0.265, 1.55); }
Note: Use ease-out for entrances, ease-in for exits, ease-in-out for movement. Tools like cubic-bezier.com help create custom curves. Material Design: cubic-bezier(0.4, 0.0, 0.2, 1).

4. Scroll-triggered Animations Experimental

Feature Syntax Description Browser Support
animation-timeline scroll() | view() Link animation to scroll position Chrome 115+
scroll() scroll(axis container) Scroll container timeline Limited
view() view(axis inset) Element in viewport timeline Limited
animation-range start-point end-point When animation plays during scroll Chrome 115+
animation-range-start cover 0% | contain 25% When animation starts Chrome 115+
animation-range-end cover 100% | contain 75% When animation ends Chrome 115+

Example: Scroll-driven animations (modern CSS)

/* Fade in as element enters viewport */
@keyframes fadeInScroll {
    from {
        opacity: 0;
        transform: translateY(50px);
    }
    to {
        opacity: 1;
        transform: translateY(0);
    }
}

.scroll-reveal {
    animation: fadeInScroll linear;
    animation-timeline: view();
    animation-range: entry 0% entry 100%;
}

/* Parallax effect with scroll */
@keyframes parallax {
    to {
        transform: translateY(-100px);
    }
}

.parallax-bg {
    animation: parallax linear;
    animation-timeline: scroll();
}

/* Scale based on scroll position */
@keyframes scaleOnScroll {
    from {
        transform: scale(0.8);
    }
    to {
        transform: scale(1);
    }
}

.scale-scroll {
    animation: scaleOnScroll linear;
    animation-timeline: view();
    animation-range: entry 0% cover 50%;
}

/* Header shrink on scroll */
@keyframes shrinkHeader {
    to {
        transform: scale(0.9);
        padding: 0.5rem 1rem;
    }
}

.header {
    animation: shrinkHeader linear;
    animation-timeline: scroll(root block);
    animation-range: 0 100px;
}

/* Rotate on scroll */
@keyframes rotateScroll {
    to {
        transform: rotate(360deg);
    }
}

.rotate-element {
    animation: rotateScroll linear;
    animation-timeline: scroll();
}

/* Progress bar based on scroll */
.progress-bar {
    position: fixed;
    top: 0;
    left: 0;
    height: 4px;
    background: blue;
    transform-origin: 0 50%;
    animation: scaleProgress linear;
    animation-timeline: scroll(root block);
}

@keyframes scaleProgress {
    from {
        transform: scaleX(0);
    }
    to {
        transform: scaleX(1);
    }
}

Example: Scroll animations with Intersection Observer (fallback)

/* CSS animation classes */
@keyframes fadeInUp {
    from {
        opacity: 0;
        transform: translateY(30px);
    }
    to {
        opacity: 1;
        transform: translateY(0);
    }
}

.reveal {
    opacity: 0;
    transition: opacity 0.6s ease, transform 0.6s ease;
}

.reveal.active {
    animation: fadeInUp 0.6s ease forwards;
}

/* JavaScript (Intersection Observer) */
/*
const observer = new IntersectionObserver((entries) => {
    entries.forEach(entry => {
        if (entry.isIntersecting) {
            entry.target.classList.add('active');
        }
    });
}, {
    threshold: 0.1,
    rootMargin: '0px 0px -100px 0px'
});

document.querySelectorAll('.reveal').forEach(el => {
    observer.observe(el);
});
*/

/* Staggered scroll reveals */
.reveal-stagger {
    opacity: 0;
    transform: translateY(30px);
    transition: all 0.6s ease;
}

.reveal-stagger.active {
    opacity: 1;
    transform: translateY(0);
}

.reveal-stagger:nth-child(1) { transition-delay: 0.1s; }
.reveal-stagger:nth-child(2) { transition-delay: 0.2s; }
.reveal-stagger:nth-child(3) { transition-delay: 0.3s; }

/* Parallax sections */
.parallax-section {
    transform: translateY(calc(var(--scroll-progress) * -50px));
    transition: transform 0.1s linear;
}

/* Scroll-linked opacity */
.fade-on-scroll {
    opacity: calc(1 - var(--scroll-progress));
}

/* Pin element during scroll (with scroll-timeline) */
@keyframes pinSticky {
    0% {
        transform: translateY(0);
    }
    100% {
        transform: translateY(0);
    }
}

.pin-element {
    position: sticky;
    top: 0;
    animation: pinSticky linear;
    animation-timeline: view();
}
Warning: animation-timeline is experimental (Chrome 115+). Use Intersection Observer for production. Performance: limit scroll animations, use will-change, prefer transform/opacity.

5. View Transition API Integration Experimental

API Method Syntax Description Browser Support
document.startViewTransition() startViewTransition(callback) Create animated transition between states Chrome 111+
view-transition-name unique-name Identify element for transition Chrome 111+
::view-transition Pseudo-element Root transition container Chrome 111+
::view-transition-group() Pseudo-element Group for specific transition Chrome 111+
::view-transition-image-pair() Pseudo-element Old and new image container Chrome 111+
::view-transition-old() Pseudo-element Outgoing state snapshot Chrome 111+
::view-transition-new() Pseudo-element Incoming state snapshot Chrome 111+

Example: View Transition API basics

/* Mark elements for view transitions */
.card {
    view-transition-name: card-1;
}

.title {
    view-transition-name: title;
}

/* Customize transition animation */
::view-transition-old(card-1),
::view-transition-new(card-1) {
    animation-duration: 0.5s;
}

/* Custom animation for specific transition */
@keyframes slide-from-right {
    from {
        transform: translateX(100%);
    }
}

::view-transition-new(card-1) {
    animation: slide-from-right 0.5s ease-out;
}

/* Fade transition */
::view-transition-old(root) {
    animation: 0.3s ease-out both fade-out;
}

::view-transition-new(root) {
    animation: 0.3s ease-out both fade-in;
}

@keyframes fade-out {
    to {
        opacity: 0;
    }
}

@keyframes fade-in {
    from {
        opacity: 0;
    }
}

/* JavaScript usage */
/*
// Simple transition
function updateView() {
    document.startViewTransition(() => {
        // Update DOM here
        document.querySelector('.content').textContent = 'New content';
    });
}

// With promise handling
async function animatedUpdate() {
    const transition = document.startViewTransition(() => {
        // DOM updates
        updateDOMState();
    });
    
    await transition.finished;
    console.log('Transition complete');
}

// Conditional transitions
function conditionalTransition() {
    if (document.startViewTransition) {
        document.startViewTransition(() => updateDOM());
    } else {
        updateDOM();  // Fallback without animation
    }
}
*/

Example: Advanced View Transition patterns

/* Different transitions for different elements */
.hero {
    view-transition-name: hero;
}

.sidebar {
    view-transition-name: sidebar;
}

/* Hero expands */
::view-transition-new(hero) {
    animation: scaleUp 0.5s ease-out;
}

@keyframes scaleUp {
    from {
        transform: scale(0.8);
    }
}

/* Sidebar slides in */
::view-transition-new(sidebar) {
    animation: slideInLeft 0.4s ease-out;
}

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

/* Morph between layouts */
.grid-item {
    view-transition-name: attr(data-id);  /* Dynamic names */
}

/* Smooth color transitions */
::view-transition-group(root) {
    animation-duration: 0.5s;
    animation-timing-function: ease-in-out;
}

/* Page transition (SPA navigation) */
::view-transition-old(root) {
    animation: fadeOutScale 0.3s ease-in;
}

::view-transition-new(root) {
    animation: fadeInScale 0.3s ease-out;
}

@keyframes fadeOutScale {
    to {
        opacity: 0;
        transform: scale(0.95);
    }
}

@keyframes fadeInScale {
    from {
        opacity: 0;
        transform: scale(1.05);
    }
}

/* Disable transition for specific elements */
.no-transition {
    view-transition-name: none;
}

/* Cross-fade between images */
.image-container {
    view-transition-name: main-image;
}

::view-transition-old(main-image),
::view-transition-new(main-image) {
    animation-duration: 0.4s;
    height: 100%;
    object-fit: cover;
}

/* Custom easing for smooth transitions */
::view-transition-group(*) {
    animation-timing-function: cubic-bezier(0.4, 0.0, 0.2, 1);
}
Warning: View Transitions API is experimental (Chrome 111+). Not yet in Firefox/Safari. Always provide fallback. Only works for same-document transitions. Feature detection required: if (document.startViewTransition) { }

6. Performance-optimized Animations

Optimization Technique Benefit When to Use
GPU Properties Animate transform and opacity only 60fps performance, no layout/paint All animations
will-change will-change: transform Prepare GPU layer in advance Before animation starts
contain contain: layout paint Isolate animation effects Complex animated sections
Reduce complexity Limit animated elements Less work per frame Many simultaneous animations
requestAnimationFrame JS animations synced to browser Smooth frame timing JavaScript-based animations
Debounce scroll Throttle scroll event handlers Reduce event handler calls Scroll-triggered animations
Property Performance Impact Triggers Recommendation
transform Excellent Composite only ✓ Use for movement
opacity Excellent Composite only ✓ Use for fades
filter Good Paint + Composite ⚠ Use sparingly
width/height Poor Layout + Paint + Composite ✗ Avoid animating
top/left/right/bottom Poor Layout + Paint + Composite ✗ Use transform instead
margin/padding Poor Layout + Paint + Composite ✗ Use transform instead
background-color Moderate Paint + Composite ⚠ Short durations only

Example: Performance-optimized animations

/* GOOD: GPU-accelerated */
.optimized {
    transition: transform 0.3s, opacity 0.3s;
}

.optimized:hover {
    transform: translateY(-5px);
    opacity: 0.8;
}

/* BAD: Triggers layout */
.bad {
    transition: top 0.3s, height 0.3s;
}

.bad:hover {
    top: -5px;
    height: 150px;
}

/* will-change optimization */
.about-to-animate {
    will-change: transform, opacity;
}

.about-to-animate.animating {
    animation: slideIn 0.5s;
}

/* Remove will-change after animation */
.about-to-animate.done {
    will-change: auto;
}

/* Contain for isolated animations */
.animated-card {
    contain: layout paint;
    /* Animation won't affect outside elements */
}

/* Efficient loader */
.loader {
    width: 40px;
    height: 40px;
    border: 4px solid #f3f3f3;
    border-top: 4px solid #3498db;
    border-radius: 50%;
    animation: spin 1s linear infinite;
    /* Only animates transform - GPU accelerated */
}

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

/* Efficient pulse effect */
.pulse {
    animation: pulse 2s ease-in-out infinite;
}

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

/* Reduce animation on low-end devices */
@media (prefers-reduced-motion: reduce) {
    * {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
    }
}

/* Efficient stagger without JavaScript */
.stagger-item {
    animation: fadeIn 0.5s ease-out backwards;
}

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

@keyframes fadeIn {
    from {
        opacity: 0;
        transform: translateY(20px);
    }
}

Example: Performance best practices

/* Layer promotion for complex animations */
.complex-animation {
    transform: translateZ(0);  /* Force GPU layer */
    will-change: transform;
}

/* Clean up will-change after animation */
/*
element.addEventListener('animationend', () => {
    element.style.willChange = 'auto';
});
*/

/* Efficient scroll animations */
.scroll-animate {
    transition: transform 0.1s ease-out;
    /* Short duration for responsiveness */
}

/* Throttle expensive updates */
/*
let ticking = false;

window.addEventListener('scroll', () => {
    if (!ticking) {
        requestAnimationFrame(() => {
            updateAnimation();
            ticking = false;
        });
        ticking = true;
    }
});
*/

/* Reduce animation complexity for mobile */
@media (max-width: 768px) {
    .complex-animation {
        animation: simpleAnimation 0.3s ease;
    }
}

/* Pause animations when not visible */
/*
const observer = new IntersectionObserver((entries) => {
    entries.forEach(entry => {
        if (entry.isIntersecting) {
            entry.target.style.animationPlayState = 'running';
        } else {
            entry.target.style.animationPlayState = 'paused';
        }
    });
});
*/

/* Efficient color transitions */
.color-change {
    /* Background-color is paint-only, relatively cheap */
    transition: background-color 0.2s ease;
}

/* Avoid filter animations on mobile */
@media (hover: none) {
    .filtered {
        /* Remove expensive filter animations on touch devices */
        filter: none;
    }
}

/* Use animation instead of transition for loops */
.infinite-loop {
    /* Animation is more efficient than transitioning back and forth */
    animation: rotate 2s linear infinite;
}

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

/* Batch DOM reads and writes */
/*
// BAD: Causes layout thrashing
elements.forEach(el => {
    const height = el.offsetHeight;  // Read
    el.style.height = height * 2 + 'px';  // Write
});

// GOOD: Batch reads, then writes
const heights = elements.map(el => el.offsetHeight);  // All reads
heights.forEach((height, i) => {
    elements[i].style.height = height * 2 + 'px';  // All writes
});
*/

Animation & Transition Best Practices

  • Only animate transform and opacity for 60fps performance
  • Use transition for state changes, animation for complex sequences
  • Use ease-out for entrances, ease-in for exits, ease-in-out for movement
  • Apply will-change temporarily before animations, remove after
  • Use animation-fill-mode: forwards to keep final state
  • Respect prefers-reduced-motion for accessibility
  • Keep durations 200-500ms for UI interactions, 1-2s for attention-grabbing
  • Use steps() for sprite animations, cubic-bezier() for custom easing
  • Test animations on low-end devices, provide simpler fallbacks