Donnez de la vie à vos interfaces avec des transitions CSS fluides. Apprenez à animer les changements de propriétés, maîtrisez les fonctions de timing et créez des effets visuels élégants.
Les transitions transforment des changements brusques en animations fluides. Au lieu qu'un bouton change instantanément de couleur au survol, la transition intercale des images intermédiaires sur la durée spécifiée.
| Propriété | Description | Exemple |
|---|---|---|
transition-property |
Quelle(s) propriété(s) animer | background, transform |
transition-duration |
Combien de temps dure l'animation | 0.3s, 300ms |
transition-timing-function |
La courbe de vitesse | ease, cubic-bezier() |
transition-delay |
Délai avant le début | 0.1s |
CYCLE DE VIE D'UNE TRANSITION
═══════════════════════════════════════════════
État initial Animation État final
(hover: off) → (en cours) → (hover: on)
┌───────────┐ ┌───────────┐ ┌───────────┐
│ │ │ ▓▓▓▓▓▓▓▓▓ │ │ │
│ BLEU │ ──→ │ ▓MIXTE▓▓▓ │ ──→ │ ROUGE │
│ │ │ ▓▓▓▓▓▓▓▓▓ │ │ │
└───────────┘ └───────────┘ └───────────┘
0% 50% 100%
Déclencheurs :
• :hover
• :focus
• :active
• Classe ajoutée via JS
• Changement de propriété via JS
Le navigateur intercale automatiquement les états intermédiaires
| Transitionnables | Non transitionnables | Attention (layout trigger) |
|---|---|---|
| color, background-color | font-family | width, height |
| transform, opacity | display | top, left, right, bottom |
| box-shadow | visibility | margin, padding |
| border-color, outline | position | border-width |
| font-size, line-height | z-index | grid-template-columns |
| text-shadow, filter | overflow | flex-basis |
transform et opacity — elles sont accélérées par le GPU et ne déclenchent ni layout ni paint.
/* Toutes les propriétés transitionnables */
transition-property: all;
/* Une propriété spécifique */
transition-property: background-color;
/* Plusieurs propriétés */
transition-property: background-color, transform, opacity;
/* Aucune (désactive la transition) */
transition-property: none;
s) ou millisecondes (ms).
transition-duration: 0.3s; /* 300 millisecondes */
transition-duration: 300ms; /* Identique */
transition-duration: 0s; /* Pas d'animation (défaut) */
transition-duration: 1.5s; /* Animation lente */
| Durée | Impression | Usage |
|---|---|---|
0.1s – 0.2s |
Rapide, subtil | Effets de focus, micro-interactions |
0.2s – 0.3s |
Naturel, confortable | Hover, toggle, couleur |
0.3s – 0.5s |
Visible, lent | Apparitions, slides, cartes |
0.5s – 1s |
Lent, spectaculaire | Animations de page, transitions majeures |
> 1s |
Très lent | Événements spéciaux, loading |
Défaut. Lent au début, rapide au milieu, ralentit à la fin.
Vitesse constante du début à la fin.
Lent au début, accélère. Idéal pour les départs.
Rapide au début, ralentit. Idéal pour les arrivées.
Lent début et fin, rapide au milieu.
/* cubic-bezier(x1, y1, x2, y2) */
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); /* Material Design */
transition-timing-function: cubic-bezier(0.25, 0.8, 0.25, 1); /* Apple-like */
transition-timing-function: cubic-bezier(0.68, -0.55, 0.27, 1.55); /* Élastique */
transition-timing-function: cubic-bezier(0.1, 0.7, 0.1, 1.0); /* Sortie douce */
COURBES CUBIC-BEZIER
═══════════════════════════════════════════════
Vitesse cubic-bezier(x1, y1, x2, y2)
▲
│ ╭────────── ease (0.25, 0.1, 0.25, 1.0)
│ ╭─╯
│ ╭─╯ ──── linear
│ ╭─╯
│╭─╯ ╭─────── ease-in-out
│╯ ╭─╯
│──╯───── ease-in
│────────── ease-out
└────────────────────────► Temps
x1, y1 = point de contrôle 1 (début)
x2, y2 = point de contrôle 2 (fin)
Outils en ligne : cubic-bezier.com
Les courbes de timing et leur effet sur la vitesse
/* steps(nombre, direction) */
transition-timing-function: steps(5); /* 5 pas, fin en last */
transition-timing-function: steps(5, start); /* 5 pas, début en first */
transition-timing-function: steps(5, end); /* 5 pas, fin en last (défaut) */
/* Exemple : compteur digital */
.compteur {
transition: content 0.5s steps(10);
}
transition-delay: 0s; /* Pas de délai (défaut) */
transition-delay: 0.2s; /* Attend 200ms avant de commencer */
transition-delay: 500ms; /* Attend 500ms */
/* transition: property duration timing-function delay; */
/* Exemple complet */
.btn {
transition: background-color 0.3s ease 0s,
transform 0.2s ease 0.1s;
}
/* Shorthand pour tout */
.btn {
transition: background-color 0.3s ease,
transform 0.2s ease 0.1s;
}
/* Toutes les propriétés, même durée, même courbe */
.btn {
transition: all 0.3s ease;
}
/* Valeur raccourcie avec 2 temps */
/* duration | delay */
.btn {
transition: all 0.3s 0.1s; /* 0.3s de durée, 0.1s de délai */
}
DÉCLENCHEURS DE TRANSITION
═══════════════════════════════════════════════
1. :hover ──────── Survole de la souris
2. :focus ──────── Focus clavier/clic
3. :active ─────── Clic maintenu
4. Classe ajoutée ─── JS: element.classList.add('active')
5. Animation initiale ─── Au chargement de la page
La transition se joue dans les DEUX sens :
• Souris sur l'élément → état initial → état final
• Souris quitte l'élément → état final → état initial
(Retour toujours animé avec la même durée/courbe)
Les différents déclencheurs et le comportement aller-retour
Transform + shadow
Box-shadow uniquement
Transform: scale sur l'image
Une transition CSS anime progressivement le changement d'une propriété d'une valeur à une autre, sur une durée déterminée.
transform et opacity sont les plus performantes car elles sont accélérées par le GPU et ne déclenchent ni layout ni paint.
Pour les micro-interactions : 0.1-0.2s. Pour les hover : 0.2-0.3s. Pour les transitions visibles : 0.3-0.5s.
GPU prêt
Pas de hint GPU
Réduction de mouvement
Si votre OS est en mode "réduire les animations", cette transition est désactivée.
.bouton {
background: #2563eb;
color: white;
border: none;
padding: 0.75rem 1.5rem;
border-radius: 8px;
cursor: pointer;
transition: background 0.3s ease;
}
.bouton:hover {
background: #1d4ed8;
}
.carte {
background: white;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
transform: translateY(0);
transition: transform 0.3s ease,
box-shadow 0.3s ease;
}
.carte:hover {
transform: translateY(-4px);
box-shadow: 0 8px 20px rgba(0,0,0,0.15);
}
.menu-item {
opacity: 0;
transform: translateY(10px);
transition: opacity 0.3s ease, transform 0.3s ease;
}
/* Délai incrémental pour chaque item */
.menu-item:nth-child(1) { transition-delay: 0.05s; }
.menu-item:nth-child(2) { transition-delay: 0.10s; }
.menu-item:nth-child(3) { transition-delay: 0.15s; }
.menu-item:nth-child(4) { transition-delay: 0.20s; }
.menu.active .menu-item {
opacity: 1;
transform: translateY(0);
}
.lien {
text-decoration: none;
color: #2563eb;
position: relative;
}
.lien::after {
content: "";
position: absolute;
bottom: -2px;
left: 0;
width: 100%;
height: 2px;
background: #2563eb;
transform: scaleX(0);
transform-origin: left;
transition: transform 0.3s ease;
}
.lien:hover::after {
transform: scaleX(1);
}
.input {
border: 2px solid #e2e8f0;
border-radius: 8px;
padding: 0.75rem 1rem;
outline: none;
transition: border-color 0.3s ease,
box-shadow 0.3s ease;
}
.input:focus {
border-color: #2563eb;
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.15);
}
.toggle-track {
width: 48px;
height: 26px;
background: #cbd5e1;
border-radius: 13px;
position: relative;
cursor: pointer;
transition: background 0.3s ease;
}
.toggle-track.active {
background: #16a34a;
}
.toggle-thumb {
position: absolute;
top: 3px;
left: 3px;
width: 20px;
height: 20px;
background: white;
border-radius: 50%;
box-shadow: 0 1px 3px rgba(0,0,0,0.2);
transition: transform 0.3s ease;
}
.toggle-track.active .toggle-thumb {
transform: translateX(22px);
}
/* ❌ Lent — déclenche layout + paint */
.bloc {
transition: width 0.3s ease, /* Layout trigger */
height 0.3s ease, /* Layout trigger */
margin 0.3s ease, /* Layout trigger */
top 0.3s ease, /* Layout trigger */
padding 0.3s ease; /* Layout trigger */
}
/* ✅ Rapide — GPU accelerated */
.bloc {
transition: transform 0.3s ease, /* GPU ✓ */
opacity 0.3s ease; /* GPU ✓ */
}
/* Quand width/height sont indispensables, utiliser
transform: scale() ou max-width à la place */
/* ❌ Transitionne TOUT, même les propriétés non voulues */
.btn {
transition: all 0.3s ease;
}
/* Si un media query change le padding, ça anime aussi ! */
/* ✅ Cibler les propriétés spécifiques */
.btn {
transition: background-color 0.3s ease,
transform 0.2s ease;
}
/* ❌ Animation sur l'état hover mais pas au retour */
.btn:hover {
transform: scale(1.1);
/* Le retour est brutal car la transition est
définie sur .btn, pas sur .btn:hover */
}
/* ✅ Définir la transition sur l'état de base */
.btn {
transition: transform 0.3s ease;
}
.btn:hover {
transform: scale(1.1);
}
/* ❌ L'utilisateur attend que l'animation finisse */
.btn {
transition: all 1.5s ease; /* Trop lent ! */
}
/* ✅ Rapide et confortable */
.btn {
transition: background 0.2s ease; /* Micro-interaction */
}
.carte {
transition: transform 0.3s ease; /* Effet hover */
}
/* ❌ Pas d'alternative pour les utilisateurs sensibles */
.anime {
animation: spin 2s linear infinite;
}
/* ✅ Toujours respecter la préférence */
@media (prefers-reduced-motion: reduce) {
.anime {
animation: none;
transition: none;
}
}
/* ❌ Mettre will-change sur tout */
.bloc {
will-change: transform, opacity, width, height, color;
}
/* ✅ Utiliser uniquement sur les propriétés animées,
et idéalement pendant la phase d'animation */
.bloc-animate {
will-change: transform;
}
/* Retirer après l'animation si elle est ponctuelle */
/* Définir des tokens de transition */
:root {
--transition-fast: 0.15s ease;
--transition-base: 0.25s ease;
--transition-slow: 0.4s ease;
--transition-spring: 0.5s cubic-bezier(0.34, 1.56, 0.64, 1);
}
/* Utiliser les tokens partout */
.btn {
transition: background var(--transition-fast),
transform var(--transition-base);
}
.carte {
transition: transform var(--transition-base),
box-shadow var(--transition-base);
}
.modal {
transition: opacity var(--transition-slow),
transform var(--transition-slow);
}
transform — GPU accelerated, ne déclenche pas le layoutopacity — GPU accelerated, très légercolor, background-color — Paint uniquementbox-shadow, text-shadow — Paint uniquementborder-color, outline — Paint uniquementwidth, height, margin, padding — Layout triggertop, left, right, bottom — Layout trigger0.15-0.2s0.2-0.3s0.3-0.5s0.5-1s/* Soulèvement de carte */
.carte:hover {
transform: translateY(-4px);
box-shadow: 0 8px 20px rgba(0,0,0,0.12);
}
/* Soulignement de lien */
.lien::after {
transform: scaleX(0);
transform-origin: left;
transition: transform 0.3s ease;
}
.lien:hover::after { transform: scaleX(1); }
/* Scale au focus */
.input:focus {
transform: scale(1.02);
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.15);
}
/* Fade + slide au survol */
.overlay {
opacity: 0;
transform: translateY(10px);
transition: opacity 0.3s ease, transform 0.3s ease;
}
.carte:hover .overlay {
opacity: 1;
transform: translateY(0);
}
Défi 1 : Bouton avec 3 effets
Créez un bouton qui change de couleur, d'ombre et de taille au survol, avec des durées différentes pour chaque propriété.
.bouton {
background: #2563eb;
color: white;
padding: 0.75rem 1.5rem;
border: none;
border-radius: 8px;
cursor: pointer;
transition: background-color 0.2s ease,
box-shadow 0.3s ease 0.05s,
transform 0.15s ease;
}
.bouton:hover {
background: #1d4ed8;
box-shadow: 0 8px 20px rgba(37, 99, 235, 0.3);
transform: translateY(-2px);
}
Défi 2 : Card avec overlay au survol
Créez une carte image qui révèle une couche noire semi-transparente avec titre et description au survol.
Défi 3 : Input focus animé
Créez un champ de formulaire avec une bordure qui change de couleur et un glow au focus, le tout avec une transition fluide.
Défi 4 : Menu hamburger animé
Créez un bouton hamburger (3 barres) qui se transforme en X au clic avec une animation fluide.
.hamburger {
width: 30px;
height: 20px;
position: relative;
cursor: pointer;
}
.hamburger span {
display: block;
width: 100%;
height: 3px;
background: #1e293b;
position: absolute;
transition: transform 0.3s ease, opacity 0.2s ease;
}
.hamburger span:nth-child(1) { top: 0; }
.hamburger span:nth-child(2) { top: 8px; }
.hamburger span:nth-child(3) { top: 16px; }
.hamburger.active span:nth-child(1) {
transform: rotate(45deg) translate(5px, 6px);
}
.hamburger.active span:nth-child(2) {
opacity: 0;
}
.hamburger.active span:nth-child(3) {
transform: rotate(-45deg) translate(5px, -6px);
}
Défi 5 : Accéibilité
Ajoutez prefers-reduced-motion à un de vos effets de transition pour qu'il soit désactivé pour les utilisateurs sensibles au mouvement.
Projet 1 : Galerie avec effets hover
Créez une galerie de cartes avec :
Projet 2 : Menu de navigation animé
Créez un menu complet avec :
Projet 3 : Formulaire interactif
Créez un formulaire avec :
Projet 4 : Modal avec transitions
Créez une modale avec :
| Concept | Propriété | Valeurs |
|---|---|---|
| Propriété animée | transition-property |
all, none, nom de propriété |
| Durée | transition-duration |
0.3s, 300ms |
| Courbe | transition-timing-function |
ease, linear, cubic-bezier(), steps() |
| Délai | transition-delay |
0.1s, 100ms |
| Shorthand | transition |
property duration timing delay |
transition: all 0.3s ease fait ?width et height ?ease et ease-in-out ?will-change fait ?transition-property, transition-duration, transition-timing-function, transition-delaycubic-bezier()transform et opacitytransition-delay pour chaque élément (incrémental)ease est la courbe par défaut ; ease-in-out est symétrique (lent début et fin)@media (prefers-reduced-motion: reduce)/* Transition basique */
transition: property 0.3s ease;
/* Multi-propriétés */
transition: background-color 0.2s ease,
transform 0.3s ease 0.1s,
box-shadow 0.3s ease;
/* Tokens */
:root {
--t-fast: 0.15s ease;
--t-base: 0.25s ease;
--t-slow: 0.4s ease;
}
/* Performance — toujours préférer */
transition: transform 0.3s ease, /* GPU ✓ */
opacity 0.3s ease; /* GPU ✓ */
/* Anti-pattern — à éviter */
transition: width 0.3s ease, /* Layout ✗ */
margin 0.3s ease; /* Layout ✗ */
/* Accéleré par le GPU */
transition: transform 0.3s ease;
transition: opacity 0.3s ease;
/* will-change (utiliser avec parcimonie) */
.anime { will-change: transform; }
/* prefers-reduced-motion */
@media (prefers-reduced-motion: reduce) {
* { transition: none !important; }
}
/* Soulignement de lien */
.lien::after {
transform: scaleX(0);
transform-origin: left;
transition: transform 0.3s ease;
}
.lien:hover::after { transform: scaleX(1); }
/* Soulèvement de carte */
.carte {
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.carte:hover {
transform: translateY(-4px);
box-shadow: 0 8px 20px rgba(0,0,0,0.12);
}
/* Focus glow */
.input:focus {
outline: none;
border-color: #2563eb;
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.15);
}