CSS Animations

Une interface qui bouge se comprend mieux qu’une interface qui saute. Transitions et keyframes rendent visibles les changements d’état — à condition de rester sobres.

Pourquoi animer une interface

Une animation n’est pas un ornement : elle dit ce qui vient de changer, et où regarder.

Transitions ou animations

Transition

D’un état à un autre, déclenchée par quelque chose — un survol, un clic, une classe ajoutée.

css

.bouton {
    transition: all 0.3s ease;
}

.bouton:hover {
    transform: translateY(-4px);
}

Animation

Une suite d’étapes décrites à l’avance, qui peut tourner seule et se répéter.

css

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

.element {
    animation: pulse 2s infinite;
}

Les transitions, les fondamentaux

Quatre réglages suffisent : ce qui change, en combien de temps, selon quelle courbe, et après quel délai.

La syntaxe complète

css

.element {
    transition-property: transform;
    transition-duration: 0.3s;
    transition-timing-function: ease;
    transition-delay: 0s;
}

/* Raccourci */
.element {
    transition: all 0.3s ease 0s;
}

Le raccourci prend les valeurs dans cet ordre : propriété, durée, courbe, délai.

Explorer transition-duration

transition-duration

Survolez-moi

transition-duration: ;

Sous 0,1 s le mouvement passe inaperçu ; au-delà d’une seconde il se fait attendre. 0,3 s est le réglage courant.

Explorer transition-timing-function

transition-timing-function

Survolez-moi

transition-timing-function: ;

linear avance à vitesse constante — c’est ce qui fait mécanique. ease part vite et ralentit : c’est le défaut, et c’est le plus naturel.

Explorer transition-property

transition-property

Survolez-moi

transition-property: ;

all est commode mais anime tout, y compris ce qui coûte cher. Nommer la propriété est plus sûr.

Explorer transition-delay

transition-delay

Survolez-moi

transition-delay: ;

Un délai sert à échelonner plusieurs éléments. Au-delà de 0,3 s, l’interface semble ne pas répondre.

Explorer transform

transform

Boîte

transform: ;

Les transformations se combinent dans une seule déclaration, et s’appliquent de droite à gauche.

Les keyframes, une animation qui vit seule

Une transition attend un déclencheur. Une animation décrit ses étapes une fois pour toutes et tourne d’elle-même.

Bibliothèque d’animations

@keyframes

Un mouvement qui se répète sans fin doit pouvoir être arrêté — c’est le critère 2.2.2 des règles d’accessibilité. Votre système peut déjà le demander : si vous avez réduit les animations, la galerie arrive figée.

  • bounce

    animation: bounce 1s infinite;

  • pulse

    animation: pulse 2s infinite;

  • shake

    animation: shake 0.5s infinite;

  • rotate

    animation: rotate 2s linear infinite;

  • fadeInUp

    animation: fadeInUp 1s ease-out infinite;

  • slideInRight

    animation: slideInRight 1s ease-out infinite;

  • zoomIn

    animation: zoomIn 0.5s ease-out infinite;

  • heartbeat

    animation: heartbeat 1.5s infinite;

Chaque vignette porte la déclaration qui la met en mouvement. Les dix règles @keyframes correspondantes sont plus bas.

Écrire ses propres keyframes

css

/* Définition des keyframes */
@keyframes slideIn {
    0% {
        transform: translateX(-100%);
        opacity: 0;
    }
    50% {
        transform: translateX(10px);
        opacity: 0.8;
    }
    100% {
        transform: translateX(0);
        opacity: 1;
    }
}

/* Application de l'animation */
.element {
    animation: slideIn 1s ease-out;
}

Les étapes se donnent en pourcentages, ou avec from et to pour deux étapes seulement.

Les huit réglages d’une animation

css

/* Syntaxe complète */
.element {
    animation-name: slideIn;
    animation-duration: 1s;
    animation-timing-function: ease-out;
    animation-delay: 0.2s;
    animation-iteration-count: infinite;
    animation-direction: alternate;
    animation-fill-mode: forwards;
    animation-play-state: running;
}

/* Raccourci */
.element {
    animation: slideIn 1s ease-out 0.2s infinite alternate forwards;
}

Le raccourci `animation` les prend tous, dans cet ordre.

animation-play-state, ce n’est pas un détail

C’est la propriété qui rend le bouton de pause possible. Un mouvement qui se répète sans fin doit pouvoir être arrêté — critère 2.2.2. La galerie ci-dessus en est l’exemple : un bouton, et le respect de prefers-reduced-motion.

Les indicateurs de chargement

Le cas le plus courant d’animation qui tourne en boucle — et donc celui où la pause compte le plus.

css

/* Spinner simple */
.spinner {
    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); }
}

@keyframes

Un mouvement qui se répète sans fin doit pouvoir être arrêté — c’est le critère 2.2.2 des règles d’accessibilité. Votre système peut déjà le demander : si vous avez réduit les animations, la galerie arrive figée.

  • spin

    animation: spin 1s linear infinite;

  • dotPulse

    animation: dotPulse 1.4s ease-in-out infinite both;

  • pulse

    animation: pulse 2s infinite;

  • bounce

    animation: bounce 1s infinite;

Les trois tournent en boucle : ce sont exactement les cas visés par le critère 2.2.2.

Trois usages courants

Les mêmes propriétés, appliquées à ce qu’on rencontre sur de vrais sites.

Micro-interactions

Un bouton qui réagit au survol et au clic. Le reflet qui traverse vient d’un pseudo-élément décalé, animé par sa seule position.

css

.btn-shimmer {
    position: relative;
    overflow: hidden;
    background: #3b82f6;
    transition: transform 0.1s ease;
}

.btn-shimmer::before {
    content: '';
    position: absolute;
    top: 0;
    left: -100%;
    width: 100%;
    height: 100%;
    background: linear-gradient(90deg, transparent, rgba(255,255,255,0.2), transparent);
    transition: left 0.5s ease;
}

.btn-shimmer:hover::before {
    left: 100%;
}

.btn-shimmer:active {
    transform: scale(0.95);
}

Survolez puis cliquez : la courbe change la façon dont le bouton répond.

Transitions de page

Le contenu entre en glissant, et les éléments d’une liste se suivent au lieu d’arriver ensemble. Le décalage se règle avec animation-delay.

css

.page-enter {
    opacity: 0;
    transform: translateY(30px);
    animation: pageEnter 0.6s ease-out forwards;
}

@keyframes pageEnter {
    to {
        opacity: 1;
        transform: translateY(0);
    }
}

/* Décalage en cascade */
.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; }
.stagger-item:nth-child(4) { animation-delay: 0.4s; }
.stagger-item:nth-child(5) { animation-delay: 0.5s; }

Courbes personnalisées

Au-delà des cinq courbes nommées, cubic-bezier() en décrit n’importe laquelle. Une valeur hors de [0, 1] fait dépasser la cible — c’est ce qui donne l’effet élastique.

css

/* Carte qui se soulève */
.card-effect {
    transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
}

/* Rebond élastique */
.elastique {
    transition: all 0.3s cubic-bezier(0.68, -0.6, 0.32, 1.6);
}

/* Reflet qui traverse */
.reflet {
    transition: transform 0.5s ease;
    opacity: 0.7;
}

/* Raccourci complet */
.element {
    transition: all 0.3s ease 0s;
}

Optimisation

Deux propriétés seulement sont confiées à la carte graphique : transform et opacity. Toutes les autres forcent le navigateur à recalculer la mise en page à chaque image.

css

/* ✅ Animez transform et opacity */
.rapide {
    transform: translateX(100px);
    opacity: 0.5;
}

/* Propriétés coûteuses */
.expensive {
    left: 100px;        /* ❌ Layout */
    width: 200px;       /* ❌ Layout */
    height: 100px;      /* ❌ Layout */
    background: #ff0000;  /* ❌ Paint */
    box-shadow: 0 0 10px red; /* ❌ Paint */
}

/* Prévenir le navigateur */
.gpu-accelerated {
    will-change: transform, opacity;
    transform: translateZ(0);
}

Exercice : interface de jeu animée

La mission

Créez une interface de jeu mobile complète avec des animations fluides et des micro-interactions engageantes.

Ce qui est demandé

  • Splash screen avec logo animé
  • Menu principal : boutons avec effets hover
  • Barre de vie qui pulse et change de couleur
  • Score counter avec animation de comptage
  • Power-ups avec rotation et glow effects
  • Notifications slide-in/slide-out
  • Loading states pour les transitions
  • Celebration effects pour les victoires

Code de démarrage

html

<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Game UI - Animation Exercise</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }
        
        body {
            font-family: 'Arial', sans-serif;
            background: linear-gradient(135deg, #1a1a2e, #16213e, #0f3460);
            color: white;
            min-height: 100vh;
            overflow-x: hidden;
        }
        
        /* Splash Screen */
        .splash-screen {
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            display: flex;
            justify-content: center;
            align-items: center;
            z-index: 1000;
            /* TODO: Animation d'apparition et disparition */
        }
        
        .logo {
            font-size: 3rem;
            font-weight: bold;
            text-transform: uppercase;
            /* TODO: Animation de logo (scale + rotate + glow) */
        }
        
        /* Game Container */
        .game-container {
            max-width: 400px;
            margin: 0 auto;
            padding: 2rem;
            /* TODO: Animation d'entrée de page */
        }
        
        /* Header avec titre */
        .game-title {
            text-align: center;
            font-size: 2rem;
            font-weight: bold;
            margin-bottom: 2rem;
            background: linear-gradient(45deg, #f1c40f, #e67e22);
            -webkit-background-clip: text;
            -webkit-text-fill-color: transparent;
            /* TODO: Animation de pulsation et brillance */
        }
        
        /* Barre de vie */
        .health-container {
            margin-bottom: 2rem;
        }
        
        .health-label {
            font-size: 0.9rem;
            margin-bottom: 0.5rem;
            opacity: 0.8;
        }
        
        .health-bar {
            background: rgba(255,255,255,0.1);
            height: 20px;
            border-radius: 10px;
            overflow: hidden;
            position: relative;
        }
        
        .health-fill {
            height: 100%;
            background: linear-gradient(90deg, #e74c3c, #f39c12, #2ecc71);
            border-radius: 10px;
            transition: width 0.5s ease;
            /* TODO: Animation de pulsation basée sur le niveau */
        }
        
        /* Score animé */
        .score-container {
            text-align: center;
            margin-bottom: 2rem;
        }
        
        .score-value {
            font-size: 2.5rem;
            font-weight: bold;
            color: #f1c40f;
            /* TODO: Animation de comptage + bounce lors des gains */
        }
        
        /* Power-ups */
        .powerups-container {
            display: flex;
            justify-content: center;
            gap: 1rem;
            margin-bottom: 2rem;
        }
        
        .powerup {
            width: 60px;
            height: 60px;
            border-radius: 12px;
            display: flex;
            align-items: center;
            justify-content: center;
            font-size: 1.5rem;
            cursor: pointer;
            transition: all 0.3s ease;
            /* TODO: Animations différentes pour chaque power-up */
        }
        
        .powerup-lightning {
            background: linear-gradient(135deg, #3498db, #2980b9);
            /* TODO: Animation de rotation */
        }
        
        .powerup-fire {
            background: linear-gradient(135deg, #e74c3c, #c0392b);
            /* TODO: Animation de flamme (scale + glow) */
        }
        
        .powerup-gem {
            background: linear-gradient(135deg, #9b59b6, #8e44ad);
            /* TODO: Animation de brillance */
        }
        
        /* Boutons de jeu */
        .game-buttons {
            display: flex;
            flex-direction: column;
            gap: 1rem;
        }
        
        .btn {
            padding: 1rem;
            border: none;
            border-radius: 12px;
            font-size: 1.1rem;
            font-weight: bold;
            cursor: pointer;
            position: relative;
            overflow: hidden;
            transition: all 0.2s ease;
            /* TODO: Micro-interactions (hover + active) */
        }
        
        .btn-primary {
            background: linear-gradient(135deg, #2ecc71, #27ae60);
            color: white;
        }
        
        .btn-secondary {
            background: linear-gradient(135deg, #3498db, #2980b9);
            color: white;
        }
        
        /* Notifications */
        .notification {
            position: fixed;
            top: 2rem;
            right: 2rem;
            background: linear-gradient(135deg, #f39c12, #e67e22);
            color: white;
            padding: 0.8rem 1.2rem;
            border-radius: 25px;
            font-weight: bold;
            transform: translateX(100%);
            /* TODO: Animation slide-in/slide-out */
        }
        
        /* Loading spinner */
        .loading {
            display: none;
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            z-index: 999;
        }
        
        .spinner {
            width: 50px;
            height: 50px;
            border: 4px solid rgba(255,255,255,0.3);
            border-top: 4px solid #f1c40f;
            border-radius: 50%;
            /* TODO: Animation de rotation */
        }
        
        /* Keyframes à définir */
        
        /* TODO: @keyframes pour splash screen */
        /* TODO: @keyframes pour logo animation */
        /* TODO: @keyframes pour health bar pulse */
        /* TODO: @keyframes pour score counting */
        /* TODO: @keyframes pour power-ups effects */
        /* TODO: @keyframes pour notifications */
        /* TODO: @keyframes pour celebration effects */
        
        /* Responsive */
        @media (max-width: 480px) {
            .game-container {
                padding: 1rem;
            }
            
            .game-title {
                font-size: 1.5rem;
            }
            
            .powerup {
                width: 50px;
                height: 50px;
                font-size: 1.2rem;
            }
        }
        
        /* Accessibility */
        @media (prefers-reduced-motion: reduce) {
            *, *::before, *::after {
                animation-duration: 0.01ms !important;
                animation-iteration-count: 1 !important;
                transition-duration: 0.01ms !important;
            }
        }
    </style>
</head>
<body>
    <!-- Splash Screen -->
    <div class="splash-screen" id="splashScreen">
        <div class="logo" id="logo">Super Game</div>
    </div>

    <!-- Game UI -->
    <div class="game-container" id="gameContainer">
        <h1 class="game-title">Super Game</h1>
        
        <!-- Health Bar -->
        <div class="health-container">
            <div class="health-label">Health:</div>
            <div class="health-bar">
                <div class="health-fill" id="healthFill" style="width: 75%;"></div>
            </div>
        </div>
        
        <!-- Score -->
        <div class="score-container">
            <div class="score-value" id="scoreValue">0</div>
            <div style="font-size: 0.9rem; opacity: 0.7;">Score</div>
        </div>
        
        <!-- Power-ups -->
        <div class="powerups-container">
            <div class="powerup powerup-lightning" id="powerupLightning">⚡</div>
            <div class="powerup powerup-fire" id="powerupFire">🔥</div>
            <div class="powerup powerup-gem" id="powerupGem">💎</div>
        </div>
        
        <!-- Game Buttons -->
        <div class="game-buttons">
            <button class="btn btn-primary" id="playBtn">PLAY GAME</button>
            <button class="btn btn-secondary" id="settingsBtn">Settings</button>
            <button class="btn btn-secondary" id="leaderboardBtn">Leaderboard</button>
        </div>
    </div>
    
    <!-- Notification -->
    <div class="notification" id="notification">+100 XP Earned!</div>
    
    <!-- Loading -->
    <div class="loading" id="loading">
        <div class="spinner"></div>
    </div>

    <script>
        // TODO: JavaScript pour déclencher les animations
        
        // Splash screen animation
        function hideSplashScreen() {
            // TODO: Implémenter la disparition du splash screen
        }
        
        // Score animation
        function animateScore(targetScore) {
            // TODO: Animation de comptage du score
        }
        
        // Health bar animation
        function updateHealth(percentage) {
            // TODO: Animer la barre de vie
        }
        
        // Notification system
        function showNotification(message) {
            // TODO: Afficher une notification animée
        }
        
        // Power-up effects
        function activatePowerup(type) {
            // TODO: Animation d'activation des power-ups
        }
        
        // Initialize game
        setTimeout(hideSplashScreen, 3000);
        setTimeout(() => animateScore(12450), 4000);
    </script>
</body>
</html>

Les commentaires TODO marquent ce qui reste à écrire. 320 lignes reprises telles quelles.

Les animations sont acquises

Vous savez faire réagir un élément, décrire une suite d’étapes, choisir une courbe — et rendre tout cela arrêtable. C’est la dernière des cinq pages CSS de référence.

  • 🎯 Performance

    transform et opacity d’abord : le reste force un recalcul

  • ♿ Accessibilité

    prefers-reduced-motion, et un moyen d’arrêter le mouvement

  • 🎨 Sobriété

    Une animation qui se remarque a déjà échoué

Les autres pages CSS