Donnez vie à vos interfaces avec les animations CSS. Des micro-interactions aux animations complexes, maîtrisez l'art de créer des expériences dynamiques et performantes.
Contrairement aux transitions (qui nécessitent un changement d'état déclenché), les animations sont autonomes : elles se déclenchent, se répètent et se contrôlent entièrement via CSS, sans JavaScript.
TRANSITION vs ANIMATION
═══════════════════════════════════════════════════════════
TRANSITION ANIMATION
────────── ─────────
• Nécessite un trigger • Autonome (pas de trigger)
• 2 états (avant / après) • Autant d'états que nécessaire
• Pas de répétition • Répétition illimitée
• Pas de pause • Pause possible
• Pas de delay complexe • Delays multiples
État A ──────transition────── État B @keyframes {
│ 0% { État A }
hover/click 50% { État B }
│ 100% { État A }
}
Différences fondamentales entre transitions et animations
| Besoin | Sans animation | Avec animation |
|---|---|---|
| Indicateur de chargement | Texte "Chargement..." statique | Spinner animé |
| Feedback utilisateur | Aucun retour visuel | Bouton pulse, ripple effect |
| Transition de page | Changement brutal | Slide / fade fluide |
| Attention utilisateur | Élément statique ignoré | Bounce / pulse pour attirer l'œil |
| Narration visuelle | Tout apparaît d'un coup | Apparition séquentielle (stagger) |
| État en cours | Barre de progression statique | Progression animée |
@keyframes définit les états intermédiaires d'une animation. Chaque état est identifié par un pourcentage (0% à 100%) ou par les mots-clés from (0%) et to (100%).
/* Simple : depuis → vers */
@keyframes monAnimation {
from {
transform: translateX(0);
opacity: 1;
}
to {
transform: translateX(100px);
opacity: 0;
}
}
/* Détaillé : autant d'états que nécessaire */
@keyframes monAnimation {
0% {
transform: translateX(0);
opacity: 1;
}
25% {
transform: translateX(25px);
background: #2563eb;
}
50% {
transform: translateX(50px);
background: #7c3aed;
}
75% {
transform: translateX(75px);
background: #ec4899;
}
100% {
transform: translateX(100px);
opacity: 0;
}
}
/* Les conventions courantes pour les noms */
@keyframes slide-in-left { /* snake_case */ }
@keyframes slideInLeft { /* camelCase */ }
@keyframes SLIDE-IN-LEFT { /* kebab-case (non conventionnel) */ }
/* Recommandation : utiliser kebab-case ou snake-case */
@keyframes slide-in-left { ... }
@keyframes fade-out { ... }
@keyframes scale-up { ... }
@keyframes rotate-cw { ... }
PROPRIÉTÉS ANIMABLES (sélection) ═══════════════════════════════════ ✓ TRANSFORMS transform, rotate, scale, skew, translate ✓ COULEURS color, background-color, border-color ✓ OPACITÉ opacity ✓ TAILLES width, height, max-width, min-width ✓ MARGES/PADDING margin, padding (tous les côtés) ✓ BORDURES border-width, border-radius ✓ POLICES font-size, font-weight, letter-spacing ✓ OMBRES box-shadow, text-shadow ✓ POSITION top, right, bottom, left ✓ Z-INDEX z-index ✓ FILTRES filter, backdrop-filter ✓ CLIP-PATH clip-path ✓ OBJECT-FIT object-fit ✗ display (ne s'anime pas directement) ✗ visibility (découpe, pas de transition fluide) ✗ font-family (changement brutal) ✗ overflow (pas d'animation fluide) ✗ position (sauf top/right/bottom/left)
Guide des propriétés animables vs non-animables
@keyframes.
.element {
animation-name: mon-animation;
/* ou plusieurs animations : */
animation-name: pulse, slide-in;
}
.element {
animation-duration: 2s; /* 2 secondes */
animation-duration: 500ms; /* 500 millisecondes */
animation-duration: 1.5s; /* 1.5 secondes */
}
.element {
animation-timing-function: linear; /* Vitesse constante */
animation-timing-function: ease; /* Lent → rapide → lent (défaut) */
animation-timing-function: ease-in; /* Lent → rapide */
animation-timing-function: ease-out; /* Rapide → lent */
animation-timing-function: ease-in-out; /* Lent → rapide → lent (plus marqué) */
animation-timing-function: cubic-bezier(0.68, -0.55, 0.27, 1.55); /* Personnalisée */
animation-timing-function: steps(4, end); /* 4 étapes */
}
Visualiseur de timing functions
.element {
animation-delay: 0s; /* Démarre immédiatement (défaut) */
animation-delay: 1s; /* Attend 1 seconde */
animation-delay: -0.5s; /* Démarre à 50% pour une animation de 1s */
animation-delay: 200ms; /* Attend 200ms */
}
infinite crée une boucle infinie.
.element {
animation-iteration-count: 1; /* Une fois (défaut) */
animation-iteration-count: 3; /* 3 fois */
animation-iteration-count: 0.5; /* Demi-tour (s'arrête à 50%) */
animation-iteration-count: infinite; /* Boucle infinie */
}
.element {
animation-direction: normal; /* 0%→100% à chaque fois (défaut) */
animation-direction: reverse; /* 100%→0% à chaque fois */
animation-direction: alternate; /* Aller-retour (0→100, 100→0, 0→100...) */
animation-direction: alternate-reverse; /* Retour-alterner (100→0, 0→100...) */
}
animation-direction visualisée (2 cycles)
══════════════════════════════════════════
normal: 0%───►100% │ 0%───►100%
→ → →
reverse: 100%◄───0% │ 100%◄───0%
← ← ←
alternate: 0%───►100% │ 100%◄───0%
→ → → → → ← ← ←
alternate-reverse: 100%◄───0% │ 0%───►100%
← ← ← ← ← → → →
Les 4 directions d'animation sur 2 cycles
.element {
animation-fill-mode: none; /* Pas de remplissage (défaut) */
animation-fill-mode: forwards; /* Garde l'état final (100%) */
animation-fill-mode: backwards; /* Applique l'état initial (0%) pendant le delay */
animation-fill-mode: both; /* forwards + backwards combinés */
}
animation-fill-mode visualisé
═════════════════════════════
◄── delay ──►◄── animation ──►◄── après ──►
none: [état naturel] [animation] [état naturel]
Le style "saute" au début et à la fin
forwards: [état naturel] [animation] [état final (100%)]
Garde le dernier état
backwards: [état initial] [animation] [état naturel]
Le 0% s'applique pendant le delay
both: [état initial] [animation] [état final (100%)]
Les deux combinés
L'effet de animation-fill-mode sur le rendu
.element {
animation-play-state: running; /* En cours (défaut) */
animation-play-state: paused; /* En pause */
}
/* Pause au survol */
.element:hover {
animation-play-state: paused;
}
animation est un raccourci qui combine toutes les propriétés d'animation en une seule ligne.
/* Syntaxe : animation: [name] [duration] [timing] [delay] [iteration] [direction] [fill-mode] [play-state] */
.element {
animation: pulse 1s ease-in-out 0.5s infinite alternate forwards running;
}
/* Exemples pratiques */
.fade-in { animation: fade-in 0.5s ease-out; }
.loop { animation: spin 2s linear infinite; }
.bounce { animation: bounce 0.6s cubic-bezier(0.34, 1.56, 0.64, 1) infinite; }
.pulse { animation: pulse 2s ease-in-out infinite; }
.shake { animation: shake 0.5s ease-in-out; }
/* Plusieurs animations séparées par des virgules */
.multi {
animation: pulse 2s ease infinite,
glow 3s ease-in-out infinite;
}
/* Valeur prédéfinie */
.element {
animation: none; /* Annule toutes les animations */
}
Démonstration : Les propriétés d'animation
transform applique des transformations 2D (et 3D) à un élément : translations, rotations, échelles, déformations. C'est la base de la plupart des animations performantes.
/* Translation 2D */
.element { transform: translateX(50px); } /* Déplacement horizontal */
.element { transform: translateY(-30px); } /* Déplacement vertical */
.element { transform: translate(50px, -30px); } /* Les deux axes */
/* Translation 3D */
.element { transform: translate3d(50px, -30px, 100px); } /* Ajoute la profondeur */
/* Pourcentages (relatifs à la taille de l'élément) */
.element { transform: translate(-50%, -50%); } /* Centrage classique */
/* Rotation 2D */
.element { transform: rotate(45deg); } /* Rotation horaire */
.element { transform: rotate(-90deg); } /* Rotation anti-horaire */
.element { transform: rotate(0.5turn); } /* Demi-tour */
/* Rotation 3D */
.element { transform: rotateX(45deg); } /* Rotation autour de l'axe X */
.element { transform: rotateY(45deg); } /* Rotation autour de l'axe Y */
.element { transform: rotateZ(45deg); } /* Même que rotate() 2D */
/* Échelle uniforme */
.element { transform: scale(1.5); } /* Agrandi de 50% */
.element { transform: scale(0.8); } /* Réduit de 20% */
.element { transform: scale(1); } /* Taille originale */
/* Échelle non uniforme */
.element { transform: scaleX(2); } /* Étiré en largeur */
.element { transform: scaleY(0.5); } /* Aplati en hauteur */
.element { transform: scale(1.5, 0.8); } /* Les deux axes */
/* Échelle 3D */
.element { transform: scale3d(1.5, 1.5, 2); } /* Ajoute la profondeur */
/* Skew 2D */
.element { transform: skewX(15deg); } /* Inclinaison horizontale */
.element { transform: skewY(-10deg); } /* Inclinaison verticale */
.element { transform: skew(15deg, -5deg); } /* Les deux axes */
/* matrix(a, b, c, d, tx, ty) — Combinaison de toutes les transforms 2D */
.element { transform: matrix(1, 0, 0, 1, 50, 0); } /* = translateX(50px) */
/* Utile pour des calculs programmatiques, rarement écrit à la main */
/* Définit le point autour duquel les transforms s'appliquent */
.element {
transform-origin: center; /* Centre (défaut) */
transform-origin: top left; /* Coin supérieur gauche */
transform-origin: 50% 50%; /* Centre (en pourcentage) */
transform-origin: 0 50%; /* Gauche, centre vertical */
transform-origin: bottom right; /* Coin inférieur droit */
transform-origin: 20px 40px; /* Position précise en px */
transform-origin: bottom; /* Centre horizontal, bas */
}
/* Profondeur perspective */
.scene {
perspective: 800px; /* Distance de la caméra */
}
.card {
transform-style: preserve-3d; /* Les enfants sont en 3D */
backface-visibility: hidden; /* Masque le dos de l'élément */
transform: rotateY(180deg); /* Retourne la carte */
}
/* Exemple : carte qui se retourne */
.card:hover {
transform: rotateY(180deg);
}
.card .front { backface-visibility: hidden; }
.card .back {
backface-visibility: hidden;
transform: rotateY(180deg);
}
Démonstration : Transformations (survol pour voir l'effet)
| Transform | Syntaxe | Description |
|---|---|---|
translate() |
translate(x, y) |
Déplacement horizontal et vertical |
rotate() |
rotate(angle) |
Rotation dans le plan 2D |
scale() |
scale(x, y) |
Agrandissement ou réduction |
skew() |
skew(x, y) |
Déformation (cisaillement) |
matrix() |
matrix(a,b,c,d,tx,ty) |
Transformation complète 2D |
perspective() |
perspective(px) |
Profondeur 3D |
transform-origin |
center, top left… |
Point d'origine des transformations |
transform-style |
preserve-3d, flat |
Style des enfants (3D ou plat) |
backface-visibility |
visible, hidden |
Visibilité du dos de l'élément |
╔═══════════════════════════════════════════════════════════════════╗ ║ PROPRIÉTÉS D'ANIMATION ║ ╠═══════════════════════════════════════════════════════════════════╣ ║ animation-name: nom-du-keyframes ║ ║ animation-duration: 2s | 500ms | 1.5s ║ ║ animation-timing: ease | linear | ease-in-out | steps() ║ ║ animation-delay: 0s | 1s | -0.5s ║ ║ animation-iteration: 1 | 3 | infinite ║ ║ animation-direction: normal | reverse | alternate ║ ║ animation-fill-mode: none | forwards | backwards | both ║ ║ animation-play-state: running | paused ║ ╠═══════════════════════════════════════════════════════════════════╣ ║ SHORTHAND: ║ ║ animation: name duration timing delay iteration ║ ║ direction fill-mode play-state; ║ ╠═══════════════════════════════════════════════════════════════════╣ ║ VALEURS PAR DÉFAUT: ║ ║ name: none | duration: 0s | timing: ease | delay: 0s ║ ║ iteration: 1 | direction: normal | fill: none | play: running ║ ╚═══════════════════════════════════════════════════════════════════╝ ╔═══════════════════════════════════════════════════════════════════╗ ║ PROPRIÉTÉS TRANSFORM ║ ╠═══════════════════════════════════════════════════════════════════╣ ║ translate(x, y) translateX() translateY() translate3d()║ ║ rotate(angle) rotateX() rotateY() rotateZ() ║ ║ scale(x, y) scaleX() scaleY() scale3d() ║ ║ skew(x, y) skewX() skewY() ║ ║ matrix(a,b,c,d,tx,ty) ║ ║ transform-origin: center | top left | Xpx Ypx ║ ║ transform-style: flat | preserve-3d ║ ║ backface-visibility: visible | hidden ║ ╚═══════════════════════════════════════════════════════════════════╝
Résumé visuel de toutes les propriétés d'animation et transform
Un élément statique. Pas de feedback visuel, pas de dynamisme.
L'élément se déplace et pulse. L'interface prend vie, l'utilisateur est guidé.
Les différentes syntaxes de @keyframes
/* Spinner 1 : Anneau rotatif */
@keyframes spin {
to { transform: rotate(360deg); }
}
.spinner-ring {
width: 40px;
height: 40px;
border: 4px solid #e2e8f0;
border-top-color: #2563eb;
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
/* Spinner 2 : Points rebondissants */
@keyframes dot-bounce {
0%, 80%, 100% { transform: scale(0.6); opacity: 0.5; }
40% { transform: scale(1); opacity: 1; }
}
.spinner-dots span {
animation: dot-bounce 1.4s ease-in-out infinite;
}
.spinner-dots span:nth-child(2) { animation-delay: 0.2s; }
.spinner-dots span:nth-child(3) { animation-delay: 0.4s; }
/* Spinner 3 : Barres qui grandissent */
@keyframes bar-grow {
0%, 100% { height: 10px; }
50% { height: 35px; }
}
.spinner-bars span {
animation: bar-grow 1.2s ease-in-out infinite;
}
Rendu : Trois styles de spinner
/* Bounce */
@keyframes bounce {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-20px); }
}
/* Shake (secousse) */
@keyframes shake {
0%, 100% { transform: translateX(0); }
10%, 30%, 50%, 70%, 90% { transform: translateX(-5px); }
20%, 40%, 60%, 80% { transform: translateX(5px); }
}
/* Slide in from left */
@keyframes slide-in-left {
from { transform: translateX(-60px); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
/* Fade in/out */
@keyframes fade-in-out {
0%, 100% { opacity: 0; }
50% { opacity: 1; }
}
/* Floating (flottaison) */
@keyframes floating {
0%, 100% { transform: translateY(0) rotate(0deg); }
33% { transform: translateY(-8px) rotate(2deg); }
66% { transform: translateY(4px) rotate(-1deg); }
}
/* Heartbeat (battement de cœur) */
@keyframes heartbeat {
0%, 100% { transform: scale(1); }
14% { transform: scale(1.2); }
28% { transform: scale(1); }
42% { transform: scale(1.2); }
}
/* Wave (vague) */
@keyframes wave {
0%, 100% { transform: rotate(0deg); }
25% { transform: rotate(15deg); }
50% { transform: rotate(-10deg); }
75% { transform: rotate(5deg); }
}
Rendu : Animations pratiques
@keyframes typewrite {
from { width: 0; }
to { width: 30ch; } /* ch = largeur du caractère "0" */
}
@keyframes blink-caret {
0%, 100% { border-color: transparent; }
50% { border-color: #2563eb; }
}
.typewriter {
overflow: hidden;
border-right: 2px solid #2563eb;
white-space: nowrap;
animation:
typewrite 3s steps(30) 1s forwards,
blink-caret 0.75s step-end infinite;
width: 0;
}
Rendu : Effet Typewriter
@keyframes ripple-effect {
to {
transform: scale(4);
opacity: 0;
}
}
.ripple-btn {
position: relative;
overflow: hidden;
}
.ripple-btn .ripple {
position: absolute;
border-radius: 50%;
background: rgba(255, 255, 255, 0.4);
transform: scale(0);
animation: ripple-effect 0.6s ease-out;
pointer-events: none;
}
/* JavaScript nécessaire pour créer l'élément .ripple au clic */
Rendu : Bouton avec ripple effect (cliquez)
/* Menu hamburger animé en croix */
.hamburger {
width: 30px;
height: 20px;
position: relative;
cursor: pointer;
}
.hamburger span {
position: absolute;
width: 100%;
height: 3px;
background: #1e293b;
border-radius: 2px;
transition: all 0.3s ease;
}
.hamburger span:nth-child(1) { top: 0; }
.hamburger span:nth-child(2) { top: 50%; transform: translateY(-50%); }
.hamburger span:nth-child(3) { bottom: 0; }
/* État ouvert → transforme en X */
.hamburger.active span:nth-child(1) {
transform: translateY(9px) rotate(45deg);
}
.hamburger.active span:nth-child(2) {
opacity: 0;
}
.hamburger.active span:nth-child(3) {
transform: translateY(-9px) rotate(-45deg);
}
@keyframes fade-in {
to { opacity: 1; }
}
@keyframes modal-enter {
from {
transform: scale(0.9) translateY(20px);
opacity: 0;
}
to {
transform: scale(1) translateY(0);
opacity: 1;
}
}
.modal-overlay {
display: none;
opacity: 0;
}
.modal-overlay.active {
display: flex;
animation: fade-in 0.3s ease forwards;
}
.modal-content {
animation: modal-enter 0.3s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
transform: scale(0.9) translateY(20px);
opacity: 0;
}
Rendu : Modal animée
@keyframes toast-slide {
from { transform: translateX(110%); }
to { transform: translateX(0); }
}
.toast {
animation: toast-slide 0.4s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
}
Rendu : Toast notifications
@keyframes page-slide-in {
0% { transform: translateX(100%); opacity: 0; }
20% { transform: translateX(0); opacity: 1; }
80% { transform: translateX(0); opacity: 1; }
100% { transform: translateX(-100%); opacity: 0; }
}
.page {
animation: page-slide-in 6s ease-in-out infinite;
}
Rendu : Transition de page
@keyframes stagger-in {
from { opacity: 0; transform: translateX(-20px); }
to { opacity: 1; transform: translateX(0); }
}
.stagger-item {
opacity: 0;
animation: stagger-in 0.5s ease forwards;
}
/* Décalage progressif avec nth-child */
.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; }
Rendu : Liste avec apparition séquentielle
/* Type 1 : from/to (le plus simple) */
@keyframes fade-in {
from { opacity: 0; }
to { opacity: 1; }
}
/* Type 2 : Pourcentages (contrôle total) */
@keyframes color-cycle {
0% { background: red; }
25% { background: yellow; }
50% { background: green; }
75% { background: blue; }
100% { background: red; }
}
/* Type 3 : Points multiples pour easing complexe */
@keyframes bounce-complex {
0% { transform: translateY(0); }
20% { transform: translateY(-30px); }
40% { transform: translateY(0); }
60% { transform: translateY(-15px); }
80% { transform: translateY(0); }
100% { transform: translateY(0); }
}
/* Type 4 : Animation multi-propriétés */
@keyframes complex-anim {
0% { transform: translateX(0) rotate(0deg); opacity: 1; }
50% { transform: translateX(100px) rotate(180deg); opacity: 0.5; background: #7c3aed; }
100% { transform: translateX(0) rotate(360deg); opacity: 1; }
}
/* Type 5 : Animation infinie avec timing personnalisé */
@keyframes float {
0%, 100% {
transform: translateY(0) rotate(0deg);
box-shadow: 0 5px 15px rgba(0,0,0,0.1);
}
50% {
transform: translateY(-10px) rotate(2deg);
box-shadow: 0 15px 30px rgba(0,0,0,0.15);
}
}
Effets avant/après avec pseudo-éléments
/* FAUX — l'animation n'est pas visible */
.element {
animation-name: slide-in;
/* animation-duration non défini = 0s */
}
/* CORRECT */
.element {
animation-name: slide-in;
animation-duration: 0.5s; /* Obligatoire ! */
}
/* MAUVAIS — déclenche layout + paint à chaque frame */
@keyframes bad-anim {
from { width: 100px; margin-left: 0; top: 0; }
to { width: 200px; margin-left: 50px; top: 100px; }
}
/* BON — utilise transform + opacity (compositing uniquement) */
@keyframes good-anim {
from { transform: translateX(0) scale(1); opacity: 1; }
to { transform: translateX(100px) scale(1.2); opacity: 0.5; }
}
/* FAUX — pas de respect de l'accessibilité */
.animé {
animation: pulse 2s infinite;
}
/* CORRECT — désactiver pour les utilisateurs sensibles */
@media (prefers-reduced-motion: reduce) {
.animé {
animation: none;
/* ou animation-duration: 0.01s; */
}
}
/* RISQUE — consomme des ressources en continu */
.fantaisie {
animation: rainbow 3s infinite;
}
/* BON — limiter aux animations utiles */
.utile {
animation: loading-spin 1s linear infinite; /* Justifié */
}
.decoratif {
animation: float 3s ease-in-out 5; /* Limiter le nombre de cycles */
}
/* FAUX — le navigateur prépare à chaque frame */
.animé {
transform: translateX(0);
}
.animé.animé {
transform: translateX(100px);
}
/* BON — indiquer les propriétés qui vont changer */
.animé {
will-change: transform;
transform: translateX(0);
}
/* FAUX — performance dégradée */
@keyframes mixed {
0% { transform: translateX(0); width: 100px; }
100% { transform: translateX(100px); width: 200px; }
}
/* BON — séparer ou tout mettre dans transform */
@keyframes performant {
0% { transform: translateX(0) scaleX(1); }
100% { transform: translateX(100px) scaleX(2); }
}
/* FAUX — duration = 0s par défaut dans le shorthand */
.element {
animation: slide-in ease 1s; /* slide-in = name, ease = timing, 1s = duration */
}
/* CORRECT — toujours inclure la duration */
.element {
animation: slide-in 0.5s ease; /* name, duration, timing */
}
/* FAUX — conflits potentiels */
@keyframes initial { ... } /* "initial" est une valeur CSS */
@keyframes inherit { ... } /* "inherit" est une valeur CSS */
/* CORRECT — noms descriptifs et uniques */
@keyframes slide-in-left { ... }
@keyframes fade-out-up { ... }
@keyframes pulse-scale { ... }
| Propriété | Layout | Paint | Composite | Recommandation |
|---|---|---|---|---|
transform |
Non | Non | Oui | Privilégier |
opacity |
Non | Non | Oui | Privilégier |
width |
Oui | Oui | Oui | Éviter |
height |
Oui | Oui | Oui | Éviter |
top / left |
Oui | Oui | Oui | Utiliser transform |
margin |
Oui | Oui | Oui | Éviter |
padding |
Oui | Oui | Oui | Éviter |
font-size |
Oui | Oui | Oui | Si nécessaire |
box-shadow |
Non | Oui | Oui | Acceptable |
/* 60fps = 16ms par frame. Pour rester fluide : */
/* ✅ ANIMER : transform, opacity (composite uniquement) */
/* ⚠️ ACCEPTABLE : box-shadow, filter, clip-path */
/* ❌ ÉVITER : width, height, margin, padding, top, left */
/* Exemple performant */
.animé {
will-change: transform, opacity;
}
.animé.entrer {
animation: slide-in 0.3s ease forwards;
}
@keyframes slide-in {
from { transform: translateY(20px); opacity: 0; }
to { transform: translateY(0); opacity: 1; }
}
/* 1. Définir les keyframes */
@keyframes my-animation {
from { transform: ...; opacity: ...; }
to { transform: ...; opacity: ...; }
}
/* 2. Appliquer avec le shorthand */
.mon-element {
animation: my-animation 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94);
}
/* 3. Ajouter will-change */
.mon-element {
will-change: transform, opacity;
}
/* 4. Respecter prefers-reduced-motion */
@media (prefers-reduced-motion: reduce) {
.mon-element { animation: none; }
}
Défi 1 : Spinner personnalisé
Créez un spinner avec 3 cercles concentriques qui tournent à des vitesses différentes.
@keyframes spin { to { transform: rotate(360deg); } }
.spinner-multi {
position: relative;
width: 60px;
height: 60px;
}
.spinner-multi span {
position: absolute;
inset: 0;
border: 3px solid transparent;
border-radius: 50%;
}
.spinner-multi span:nth-child(1) {
border-top-color: #2563eb;
animation: spin 1s linear infinite;
}
.spinner-multi span:nth-child(2) {
inset: 6px;
border-top-color: #7c3aed;
animation: spin 1.5s linear infinite reverse;
}
.spinner-multi span:nth-child(3) {
inset: 12px;
border-top-color: #ec4899;
animation: spin 2s linear infinite;
}
Défi 2 : Carte flip 3D
Créez une carte qui se retourne au survol pour afficher son verso, en utilisant transform-style: preserve-3d et backface-visibility: hidden.
.card-3d {
perspective: 1000px;
width: 200px;
height: 120px;
}
.card-3d-inner {
width: 100%;
height: 100%;
position: relative;
transform-style: preserve-3d;
transition: transform 0.6s ease;
}
.card-3d:hover .card-3d-inner {
transform: rotateY(180deg);
}
.card-3d-front, .card-3d-back {
position: absolute;
inset: 0;
backface-visibility: hidden;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
}
.card-3d-back {
transform: rotateY(180deg);
}
Défi 3 : Animation de chargement de page
Créez une barre de progression animée qui se remplit de gauche à droite, puis se réinitialise en boucle.
@keyframes progress-fill {
0% { width: 0%; }
100% { width: 100%; }
}
.progress-bar {
height: 4px;
background: #e2e8f0;
border-radius: 2px;
overflow: hidden;
}
.progress-bar-fill {
height: 100%;
background: linear-gradient(90deg, #2563eb, #7c3aed);
border-radius: 2px;
animation: progress-fill 2s ease-in-out infinite;
}
Défi 4 : Notification slide-in
Créez une notification qui slide depuis la droite, reste 3 secondes, puis disparaît. Utilisez animation-fill-mode: both.
@keyframes notify-in {
from { transform: translateX(100%); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
@keyframes notify-out {
from { transform: translateX(0); opacity: 1; }
to { transform: translateX(100%); opacity: 0; }
}
.notification {
animation:
notify-in 0.3s ease forwards,
notify-out 0.3s ease 3s forwards;
}
Défi 5 : Bouton avec pulse attention
Créez un bouton CTA avec une animation pulse qui attire l'attention (scale + ombre), qui s'arrête au survol.
@keyframes pulse-attention {
0%, 100% { transform: scale(1); box-shadow: 0 0 0 0 rgba(37, 99, 235, 0.4); }
50% { transform: scale(1.05); box-shadow: 0 0 0 10px rgba(37, 99, 235, 0); }
}
.cta-pulse {
animation: pulse-attention 2s ease-in-out infinite;
}
.cta-pulse:hover {
animation-play-state: paused;
transform: scale(1.05);
}
Projet 1 : Landing page avec animations
Créez une page de lancement avec :
stagger-in sur le titre, sous-titre et boutonsfloat ou fade-in au scrollpulse attentionprefers-reduced-motionProjet 2 : Système de notifications animé
Créez un système de notifications avec :
Projet 3 : Portfolio avec animations
Créez un mini-portfolio avec :
card flip 3D au survol| Concept | Propriété | Valeurs courantes |
|---|---|---|
| Définir les états | @keyframes |
from/to, 0%, 50%, 100% |
| Nommer l'animation | animation-name |
Identifiant de keyframes |
| Durée | animation-duration |
0.5s, 1s, 2s |
| Courbe de vitesse | animation-timing-function |
ease, linear, cubic-bezier() |
| Délai | animation-delay |
0s, 1s, -0.5s |
| Répétitions | animation-iteration-count |
1, 3, infinite |
| Direction | animation-direction |
normal, reverse, alternate |
| Remplissage | animation-fill-mode |
none, forwards, both |
| Lecture/pause | animation-play-state |
running, paused |
| Raccourci | animation |
name duration timing delay iteration direction fill-mode |
| Transformation | transform |
translate(), rotate(), scale(), skew() |
| Point d'origine | transform-origin |
center, top left, Xpx Ypx |
| Performance | will-change |
transform, opacity |
| Accessibilité | prefers-reduced-motion |
reduce → animation: none |
animation-fill-mode: both ?will-change et quand l'utiliser ?animation-delay fait démarrer l'animation à mi-chemin ?alternate et alternate-reverse ?@keyframes.animation-duration — sans elle, la durée est 0s et l'animation est invisible.transform et opacity — elles n'activent que le compositeur (pas de layout ni paint).both = forwards + backwards : applique l'état initial pendant le delay ET garde l'état final après.animation-iteration-count: infinite;will-change indique au navigateur quelles propriétés vont changer, lui permettant de préparer l'accélération matérielle. À utiliser avec parcimonie.@media (prefers-reduced-motion: reduce) { .animé { animation: none; } }animation-delay: -1s; pour une animation de 2s (la négativité fait démarrer à 50%).transform-origin: top left; ou transform-origin: 50% 50%;alternate = 0→100, 100→0, 0→100… alternate-reverse = 100→0, 0→100, 100→0…/* Animation de base */
.element { animation: nom 0.5s ease; }
/* Animation infinie */
.element { animation: spin 2s linear infinite; }
/* Aller-retour infini */
.element { animation: bounce 1s ease-in-out infinite alternate; }
/* Avec delay + fill-mode */
.element { animation: fade-in 0.3s ease 0.5s forwards; }
/* Pause au survol */
.element:hover { animation-play-state: paused; }
/* Rotation */
@keyframes spin { to { transform: rotate(360deg); } }
/* Bounce */
@keyframes bounce {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-20px); }
}
/* Pulse */
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
/* Slide in from left */
@keyframes slide-in {
from { transform: translateX(-100%); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
/* Respect a11y */
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after { animation-duration: 0.01ms !important; }
}