Donner Vie à vos Interfaces Web
Résumé
Maîtrisez les animations CSS pour créer des interfaces dynamiques et engageantes. Ce guide complet couvre les transitions, les keyframes, les propriétés d’animation et les bonnes pratiques pour des animations performantes et accessibles.
Objectifs pédagogiques
- Comprendre la différence entre transitions et animations CSS
- Maîtriser la syntaxe @keyframes pour créer des animations complexes
- Utiliser les propriétés d’animation (duration, timing-function, delay, iteration)
- Optimiser les performances des animations
- Appliquer les bonnes pratiques d’accessibilité
Les animations CSS permettent de modifier progressivement les propriétés d’un élément au fil du temps, sans JavaScript. Elles améliorent l’expérience utilisateur en rendant les interfaces plus vivantes et intuitives.
Deux approches complémentaires
- Transitions : Animation simple entre deux états (déclenchée par un changement d’état)
- Animations @keyframes : Séquences complexes avec plusieurs étapes intermédiaires
Quand utiliser quoi ?
| Transitions | Animations @keyframes |
|---|---|
| Hover, focus, changements d’état | Animations continues, complexes |
| 2 états (début → fin) | États multiples (0% → 50% → 100%) |
| Déclenchement par événement | Lancement automatique possible |
Une transition crée une animation fluide lorsqu’une propriété CSS change de valeur.
Syntaxe de base
/* Propriétés individuelles */
.element {
transition-property: background-color;
transition-duration: 0.3s;
transition-timing-function: ease;
transition-delay: 0s;
}
/* Propriété raccourcie */
.element {
transition: background-color 0.3s ease 0s;
/* propriété | durée | fonction | délai */
}
Exemple pratique : Bouton interactif
.btn {
background-color: #3498db;
color: white;
padding: 12px 24px;
border: none;
border-radius: 4px;
cursor: pointer;
/* Transition sur plusieurs propriétés */
transition: background-color 0.3s ease,
transform 0.2s ease,
box-shadow 0.3s ease;
}
.btn:hover {
background-color: #2980b9;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
.btn:active {
transform: translateY(0);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
Transition sur toutes les propriétés
.element {
transition: all 0.3s ease;
}
transition: all peut affecter les performances. Préférez cibler les propriétés spécifiques.
La fonction de timing définit la courbe d’accélération de l’animation, donnant un rendu plus naturel.
Valeurs prédéfinies
.element {
/* Valeurs de base */
transition-timing-function: linear; /* Vitesse constante */
transition-timing-function: ease; /* Début lent, accélère, fin lente (défaut) */
transition-timing-function: ease-in; /* Début lent */
transition-timing-function: ease-out; /* Fin lente */
transition-timing-function: ease-in-out; /* Début et fin lents */
}
Courbes de Bézier personnalisées
.element {
/* cubic-bezier(x1, y1, x2, y2) */
transition-timing-function: cubic-bezier(0.68, -0.55, 0.265, 1.55);
}
Courbes populaires
/* Effet rebond (bounce) */
.bounce {
transition-timing-function: cubic-bezier(0.68, -0.55, 0.265, 1.55);
}
/* Effet élastique */
.elastic {
transition-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1.275);
}
/* Effet snap (accrochage) */
.snap {
transition-timing-function: cubic-bezier(0.5, 0, 0.5, 1);
}
Fonction steps() pour animations saccadées
.sprite-animation {
/* Animation en 8 étapes distinctes */
transition-timing-function: steps(8, end);
}
/* Idéal pour :
- Animations de sprites
- Effet machine à écrire
- Horloge digitale */
Les @keyframes permettent de définir des séquences d’animation avec plusieurs étapes intermédiaires.
Syntaxe
/* Définition de l'animation */
@keyframes nom-animation {
from {
/* État initial (0%) */
}
to {
/* État final (100%) */
}
}
/* Application à un élément */
.element {
animation-name: nom-animation;
animation-duration: 2s;
}
Exemple : Animation de fondu
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.card {
animation: fadeIn 0.5s ease-out;
}
Étapes multiples avec pourcentages
@keyframes pulse {
0% {
transform: scale(1);
opacity: 1;
}
50% {
transform: scale(1.1);
opacity: 0.8;
}
100% {
transform: scale(1);
opacity: 1;
}
}
.notification {
animation: pulse 2s ease-in-out infinite;
}
Animation complexe : Rebond
@keyframes bounce {
0%, 20%, 50%, 80%, 100% {
transform: translateY(0);
}
40% {
transform: translateY(-30px);
}
60% {
transform: translateY(-15px);
}
}
.bouncing-element {
animation: bounce 2s infinite;
}
Toutes les propriétés d’animation
.element {
animation-name: slideIn; /* Nom de l'animation */
animation-duration: 1s; /* Durée */
animation-timing-function: ease; /* Courbe d'accélération */
animation-delay: 0.5s; /* Délai avant démarrage */
animation-iteration-count: 3; /* Nombre de répétitions */
animation-direction: alternate; /* Direction de lecture */
animation-fill-mode: forwards; /* État final conservé */
animation-play-state: running; /* État de lecture */
}
animation-iteration-count
.element {
animation-iteration-count: 1; /* Une seule fois (défaut) */
animation-iteration-count: 3; /* Trois fois */
animation-iteration-count: infinite; /* En boucle infinie */
}
animation-direction
.element {
animation-direction: normal; /* 0% → 100% */
animation-direction: reverse; /* 100% → 0% */
animation-direction: alternate; /* 0% → 100% → 0% */
animation-direction: alternate-reverse; /* 100% → 0% → 100% */
}
animation-fill-mode
.element {
animation-fill-mode: none; /* Revient à l'état initial */
animation-fill-mode: forwards; /* Conserve l'état final */
animation-fill-mode: backwards; /* Applique l'état initial pendant le delay */
animation-fill-mode: both; /* Combine forwards et backwards */
}
Propriété raccourcie
.element {
animation: slideIn 1s ease 0.5s 3 alternate forwards;
/* name | duration | timing | delay | count | direction | fill-mode */
}
Appliquer plusieurs animations
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes slideUp {
from { transform: translateY(50px); }
to { transform: translateY(0); }
}
@keyframes scaleIn {
from { transform: scale(0.8); }
to { transform: scale(1); }
}
.card {
animation:
fadeIn 0.6s ease-out,
slideUp 0.6s ease-out,
scaleIn 0.6s ease-out;
}
Animations en séquence avec délais
.item:nth-child(1) { animation-delay: 0s; }
.item:nth-child(2) { animation-delay: 0.1s; }
.item:nth-child(3) { animation-delay: 0.2s; }
.item:nth-child(4) { animation-delay: 0.3s; }
/* Ou avec CSS custom properties */
.item {
animation: fadeIn 0.5s ease-out backwards;
animation-delay: calc(var(--index) * 0.1s);
}
Exemple : Liste animée
<ul class="animated-list">
<li style="--index: 0">Premier élément</li>
<li style="--index: 1">Deuxième élément</li>
<li style="--index: 2">Troisième élément</li>
<li style="--index: 3">Quatrième élément</li>
</ul>
@keyframes slideInLeft {
from {
opacity: 0;
transform: translateX(-30px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
.animated-list li {
animation: slideInLeft 0.4s ease-out backwards;
animation-delay: calc(var(--index) * 100ms);
}
Les propriétés transform sont idéales pour les animations car elles sont accélérées par le GPU.
Transformations 2D animables
@keyframes transformDemo {
0% {
transform: translateX(0) rotate(0deg) scale(1);
}
25% {
transform: translateX(100px) rotate(90deg) scale(1.2);
}
50% {
transform: translateX(100px) translateY(100px) rotate(180deg) scale(1);
}
75% {
transform: translateX(0) translateY(100px) rotate(270deg) scale(0.8);
}
100% {
transform: translateX(0) translateY(0) rotate(360deg) scale(1);
}
}
Animation de rotation continue
@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
.loader {
width: 40px;
height: 40px;
border: 4px solid #f3f3f3;
border-top: 4px solid #3498db;
border-radius: 50%;
animation: spin 1s linear infinite;
}
Animation 3D : Flip Card
.card-container {
perspective: 1000px;
}
.card {
transform-style: preserve-3d;
transition: transform 0.6s;
}
.card:hover {
transform: rotateY(180deg);
}
.card-front,
.card-back {
backface-visibility: hidden;
position: absolute;
width: 100%;
height: 100%;
}
.card-back {
transform: rotateY(180deg);
}
transform et opacity pour vos animations. Ces propriétés sont optimisées et ne déclenchent pas de recalcul de mise en page.
Pause et reprise avec CSS
.animated {
animation: pulse 2s infinite;
}
.animated.paused {
animation-play-state: paused;
}
Contrôle JavaScript basique
const element = document.querySelector('.animated');
// Pause
element.style.animationPlayState = 'paused';
// Reprise
element.style.animationPlayState = 'running';
// Redémarrer l'animation
element.style.animation = 'none';
element.offsetHeight; // Force un reflow
element.style.animation = 'pulse 2s infinite';
Événements d’animation
const element = document.querySelector('.animated');
// Début de l'animation
element.addEventListener('animationstart', (e) => {
console.log('Animation démarrée:', e.animationName);
});
// Fin d'une itération
element.addEventListener('animationiteration', (e) => {
console.log('Itération terminée');
});
// Fin de l'animation
element.addEventListener('animationend', (e) => {
console.log('Animation terminée');
element.classList.remove('animated');
});
Déclencher une animation au scroll
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add('animate');
}
});
}, { threshold: 0.1 });
document.querySelectorAll('.animate-on-scroll').forEach(el => {
observer.observe(el);
});
Propriétés optimisées (GPU)
/* ✅ PERFORMANT - Accéléré par GPU */
.optimized {
transform: translateX(100px);
opacity: 0.5;
}
/* ❌ COÛTEUX - Déclenche un reflow */
.expensive {
left: 100px; /* Éviter */
width: 200px; /* Éviter */
margin-left: 50px; /* Éviter */
}
will-change : Optimisation proactive
/* Indique au navigateur les propriétés qui changeront */
.will-animate {
will-change: transform, opacity;
}
/* Retirer après l'animation pour libérer les ressources */
.animation-complete {
will-change: auto;
}
will-change. L’utiliser sur trop d’éléments consomme de la mémoire GPU.
Réduire les animations sur mobile
/* Animations complètes sur desktop */
.card {
animation: complexAnimation 1s ease;
}
/* Simplifier sur mobile */
@media (max-width: 768px) {
.card {
animation: simpleAnimation 0.3s ease;
}
}
Tester les performances
- Ouvrir DevTools → Performance → Enregistrer
- Vérifier le FPS (viser 60 fps)
- Identifier les « Layout Shifts » et « Paints » excessifs
Certains utilisateurs sont sensibles aux animations (troubles vestibulaires, épilepsie). CSS permet de respecter leurs préférences système.
Désactiver les animations
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
Approche progressive (recommandée)
/* Animation par défaut */
.card {
opacity: 1;
transform: none;
}
/* Animation seulement si pas de préférence pour réduction */
@media (prefers-reduced-motion: no-preference) {
.card {
animation: fadeInUp 0.6s ease-out;
}
}
Alternative : Animations subtiles
/* Animation normale */
.notification {
animation: bounce 1s ease infinite;
}
/* Animation réduite mais visible */
@media (prefers-reduced-motion: reduce) {
.notification {
animation: gentlePulse 2s ease infinite;
}
}
@keyframes gentlePulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.8; }
}
prefers-reduced-motion: reduce activé dans les DevTools.
Entrées
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes slideInUp {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes slideInLeft {
from {
opacity: 0;
transform: translateX(-30px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
@keyframes zoomIn {
from {
opacity: 0;
transform: scale(0.9);
}
to {
opacity: 1;
transform: scale(1);
}
}
Attention et feedback
@keyframes shake {
0%, 100% { transform: translateX(0); }
10%, 30%, 50%, 70%, 90% { transform: translateX(-5px); }
20%, 40%, 60%, 80% { transform: translateX(5px); }
}
@keyframes pulse {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.05); }
}
@keyframes heartbeat {
0%, 100% { transform: scale(1); }
14% { transform: scale(1.3); }
28% { transform: scale(1); }
42% { transform: scale(1.3); }
70% { transform: scale(1); }
}
Chargement
@keyframes spinner {
to { transform: rotate(360deg); }
}
@keyframes dots {
0%, 80%, 100% { transform: scale(0); }
40% { transform: scale(1); }
}
@keyframes skeleton {
0% { background-position: -200px 0; }
100% { background-position: calc(200px + 100%) 0; }
}
HTML
<div class="loader-container">
<div class="loader">
<span></span>
<span></span>
<span></span>
</div>
<p class="loader-text">Chargement...</p>
</div>
CSS Complet
/* Container */
.loader-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 200px;
gap: 20px;
}
/* Loader */
.loader {
display: flex;
gap: 8px;
}
.loader span {
width: 16px;
height: 16px;
background-color: #3498db;
border-radius: 50%;
animation: bounce 1.4s ease-in-out infinite both;
}
.loader span:nth-child(1) {
animation-delay: -0.32s;
}
.loader span:nth-child(2) {
animation-delay: -0.16s;
}
.loader span:nth-child(3) {
animation-delay: 0s;
}
@keyframes bounce {
0%, 80%, 100% {
transform: scale(0);
}
40% {
transform: scale(1);
}
}
/* Texte animé */
.loader-text {
font-size: 14px;
color: #666;
animation: pulse 1.5s ease-in-out infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
/* Accessibilité */
@media (prefers-reduced-motion: reduce) {
.loader span {
animation: none;
opacity: 0.7;
}
.loader span:nth-child(2) {
opacity: 0.85;
}
.loader span:nth-child(3) {
opacity: 1;
}
.loader-text {
animation: none;
}
}

