Cours HTML et CSS · Module 6: Animations CSS

Performance des animations CSS - optimisation et accélération GPU

13 min de lecture
Dans cette leçon13

Bienvenue dans l'atelier des bâtisseurs de pyramides ! Tout comme les anciens Égyptiens devaient construire les pyramides efficacement pour économiser ressources et temps, nous devons créer des animations performantes qui restent fluides même sur des appareils plus faibles. Apprends les secrets de l'accélération GPU, de will-change, et comment éviter les animations lentes !

Pourquoi la performance des animations est-elle importante ?

Problème : les animations peuvent provoquer :

  • Une chute des FPS (images par seconde) en dessous de 60 FPS
  • Des ralentissements et des « saccades »
  • Une décharge de la batterie sur les appareils mobiles
  • Une mauvaise expérience utilisateur (UX)

Objectif : 60 FPS fluides = 16,67 ms par image

Analogie égyptienne : tout comme les bâtisseurs de pyramides devaient utiliser les bonnes techniques (leviers, rampes) pour soulever de lourds blocs, nous devons utiliser les bonnes propriétés CSS pour que les animations soient légères et rapides.

GPU vs CPU - comprendre le rendu

CPU (Central Processing Unit)

  • Le processeur principal de l'ordinateur
  • Polyvalent
  • Plus lent pour les graphismes

GPU (Graphics Processing Unit)

  • Le processeur graphique
  • Optimisé pour les calculs graphiques
  • Beaucoup plus rapide pour les animations et les transformations

Différence clé :

  • CPU : traitement séquentiel
  • GPU : traitement parallèle de milliers de pixels simultanément

Propriétés accélérées par le GPU (RAPIDES) vs liées au CPU (LENTES)

Accéléré par le GPU (à TOUJOURS utiliser pour les animations)

1/* Transform - toutes les fonctions */
2.fast {
3  transform: translate(100px, 50px);    /* GPU */
4  transform: scale(1.5);                /* GPU */
5  transform: rotate(45deg);             /* GPU */
6  transform: skew(10deg);               /* GPU */
7  transform: translateZ(0);             /* GPU */
8  transform: translate3d(10px, 20px, 0);/* GPU */
9}
10
11/* Opacity */
12.fast {
13  opacity: 0.5;                         /* GPU */
14}

Pourquoi elles sont rapides :

  • Elles ne provoquent pas de reflow (recalcul de la mise en page)
  • Elles ne provoquent pas de repaint (redessin)
  • Le GPU les rend dans une couche séparée (compositing layer)

Lié au CPU (à ÉVITER dans les animations)

1/* Propriétés qui modifient la mise en page */
2.slow {
3  width: 300px;        /* CPU - reflow de toute la page */
4  height: 200px;       /* CPU - reflow de toute la page */
5  margin: 20px;        /* CPU - reflow de toute la page */
6  padding: 10px;       /* CPU - reflow de toute la page */
7  top: 100px;          /* CPU - reflow de l'élément */
8  left: 50px;          /* CPU - reflow de l'élément */
9  font-size: 20px;     /* CPU - reflow du texte */
10}
11
12/* Propriétés qui modifient l'apparence */
13.slow {
14  background: red;     /* CPU - repaint */
15  color: blue;         /* CPU - repaint */
16  border: 1px solid;   /* CPU - repaint */
17  box-shadow: 0 0 10px;/* CPU - repaint (lourd !) */
18}

Pourquoi elles sont lentes :

  • Reflow = le navigateur doit recalculer la position de TOUS les éléments
  • Repaint = le navigateur doit redessiner les pixels
  • Elles bloquent le thread principal

Exemples pratiques - LENT vs RAPIDE

Exemple 1 : déplacer un élément

1/* LENT - animer left */
2.box-slow {
3  position: relative;
4  left: 0;
5  transition: left 0.3s;
6}
7
8.box-slow:hover {
9  left: 100px;  /* Provoque un reflow à chaque image ! */
10}
11
12/* RAPIDE - animer transform */
13.box-fast {
14  transform: translateX(0);
15  transition: transform 0.3s;
16}
17
18.box-fast:hover {
19  transform: translateX(100px);  /* Accéléré par le GPU ! */
20}

Résultat :

  • Lent : ~30-40 FPS (ralentissements visibles)
  • Rapide : 60 FPS (fluide)

Exemple 2 : agrandir un élément

1/* LENT - animer width/height */
2.card-slow {
3  width: 200px;
4  height: 300px;
5  transition: width 0.3s, height 0.3s;
6}
7
8.card-slow:hover {
9  width: 220px;    /* Reflow ! */
10  height: 330px;   /* Reflow ! */
11}
12
13/* RAPIDE - animer scale */
14.card-fast {
15  width: 200px;
16  height: 300px;
17  transform: scale(1);
18  transition: transform 0.3s;
19}
20
21.card-fast:hover {
22  transform: scale(1.1);  /* GPU ! 220px x 330px */
23}

Exemple 3 : apparition/disparition en fondu

1/* LENT - animer visibility */
2.modal-slow {
3  visibility: visible;
4  transition: visibility 0.3s;
5}
6
7.modal-slow.hidden {
8  visibility: hidden;  /* Ne peut pas être animé ! Saut ! */
9}
10
11/* RAPIDE - animer opacity */
12.modal-fast {
13  opacity: 1;
14  transition: opacity 0.3s;
15}
16
17.modal-fast.hidden {
18  opacity: 0;  /* Animation GPU fluide ! */
19  pointer-events: none;  /* Désactiver les interactions */
20}

Exemple 4 : box shadow (très coûteux !)

1/* TRÈS LENT - animer box-shadow */
2.button-slow {
3  box-shadow: 0 2px 4px rgba(0,0,0,0.1);
4  transition: box-shadow 0.3s;
5}
6
7.button-slow:hover {
8  box-shadow: 0 8px 16px rgba(0,0,0,0.3);  /* Très lourd ! */
9}
10
11/* PLUS RAPIDE - pseudo-élément avec opacity */
12.button-fast {
13  position: relative;
14}
15
16.button-fast::after {
17  content: '';
18  position: absolute;
19  inset: 0;
20  box-shadow: 0 8px 16px rgba(0,0,0,0.3);
21  opacity: 0;
22  transition: opacity 0.3s;
23  pointer-events: none;
24}
25
26.button-fast:hover::after {
27  opacity: 1;  /* Animer uniquement opacity ! */
28}

will-change - prévenir le navigateur des changements à venir

will-change est un indice pour le navigateur : « Hé, je vais animer cette propriété, prépare-toi ! »

Comment fonctionne will-change

1.element {
2  will-change: transform;
3  /* Le navigateur crée une couche GPU AVANT l'animation */
4}
5
6.element:hover {
7  transform: scale(1.2);  /* Déjà prêt ! */
8}

Ce qui se passe :

  1. Le navigateur crée une couche séparée (compositing layer) pour l'élément
  2. L'élément est rendu par le GPU
  3. L'animation est instantanée et fluide

Quand utiliser will-change

1/* BON - pour les éléments fréquemment animés */
2.menu-item {
3  will-change: transform;
4  transition: transform 0.2s;
5}
6
7.menu-item:hover {
8  transform: translateY(-5px);
9}
10
11/* BON - pour les animations keyframes */
12.spinner {
13  will-change: transform;
14  animation: spin 1s linear infinite;
15}
16
17@keyframes spin {
18  to { transform: rotate(360deg); }
19}
20
21/* BON - ajouter dynamiquement avant l'animation */
22.modal {
23  /* Pas de will-change par défaut */
24}
25
26.modal.opening {
27  will-change: transform, opacity;  /* Ajouter avant l'animation */
28}
29
30.modal.open {
31  will-change: auto;  /* Retirer après l'animation ! */
32}

Quand NE PAS utiliser will-change

1/* MAUVAIS - sur tous les éléments */
2* {
3  will-change: transform;  /* Utilise BEAUCOUP de mémoire ! */
4}
5
6/* MAUVAIS - sur des éléments statiques */
7.logo {
8  will-change: transform;  /* Le logo ne bouge pas ! */
9}
10
11/* MAUVAIS - trop de propriétés */
12.element {
13  will-change: transform, opacity, width, height, background, color;
14  /* Trop - contre-productif ! */
15}
16
17/* MAUVAIS - laissé en permanence */
18.button {
19  will-change: transform;  /* Laissé pour toujours = fuite de mémoire */
20}

Règles pour will-change :

  1. Uniquement pour les éléments animés
  2. Seulement 1-2 propriétés à la fois
  3. Ajouter avant l'animation, retirer après l'animation
  4. Teste l'utilisation de la mémoire - cela peut être contre-productif !

Ajouter will-change dynamiquement (JavaScript)

1const element = document.querySelector('.box');
2
3// Ajouter avant l'animation (par ex. au mouseenter)
4element.addEventListener('mouseenter', () => {
5  element.style.willChange = 'transform';
6});
7
8// Retirer après l'animation (au transitionend)
9element.addEventListener('transitionend', () => {
10  element.style.willChange = 'auto';
11});

Forcer l'accélération GPU - l'astuce translateZ(0)

Si le navigateur ne crée pas automatiquement de couche GPU, tu peux la forcer :

1.element {
2  /* Forcer une couche GPU */
3  transform: translateZ(0);
4  /* ou */
5  transform: translate3d(0, 0, 0);
6  /* ou */
7  will-change: transform;
8}

Exemple pratique :

1/* Image qui sera agrandie */
2.image {
3  transform: translateZ(0);  /* Forcer le GPU */
4  transition: transform 0.3s;
5}
6
7.image:hover {
8  transform: scale(1.1);  /* Maintenant GPU garanti */
9}

Attention : ne l'utilise pas sur tous les éléments - cela consomme de la mémoire GPU !

Éviter l'animation de width/height - le layout thrashing

Problème : animer width ou height provoque un reflow à chaque image.

Méthode lente - animation de width

1.progress-bar-slow {
2  width: 0%;
3  transition: width 2s;
4}
5
6.progress-bar-slow.complete {
7  width: 100%;  /* Reflow à chaque image ! */
8}

Méthode rapide - transform: scaleX

1.progress-bar-fast {
2  width: 100%;  /* Pleine largeur immédiatement */
3  transform: scaleX(0);  /* Échelle 0% */
4  transform-origin: left;  /* Grandir depuis la gauche */
5  transition: transform 2s;
6}
7
8.progress-bar-fast.complete {
9  transform: scaleX(1);  /* Échelle 100% - GPU ! */
10}

Alternative - clip-path

1.progress-bar-clip {
2  width: 100%;
3  background: linear-gradient(to right, #3498db, #2ecc71);
4  clip-path: inset(0 100% 0 0);  /* Cacher le côté droit */
5  transition: clip-path 2s;
6}
7
8.progress-bar-clip.complete {
9  clip-path: inset(0 0 0 0);  /* Tout afficher */
10}

requestAnimationFrame vs animations CSS

Animations CSS - PRÉFÉRÉES

Avantages :

  • Optimisées par le navigateur
  • S'exécutent dans un thread séparé (compositor thread)
  • Fluides même quand le thread principal est occupé
  • Moins de code
  • Accélération GPU automatique
1/* Animation CSS - préférée */
2.box {
3  animation: slide 1s ease-in-out infinite;
4}
5
6@keyframes slide {
7  0% { transform: translateX(0); }
8  100% { transform: translateX(100px); }
9}

requestAnimationFrame (rAF) - pour les animations complexes

Quand l'utiliser :

  • Animations dépendant de l'interaction de l'utilisateur (scroll, souris)
  • Animations avec des valeurs dynamiques (physique, easing)
  • Synchronisation de plusieurs animations
  • Animations Canvas/WebGL
1/* JavaScript rAF - pour les cas complexes */
2let position = 0;
3const box = document.querySelector('.box');
4
5function animate() {
6  position += 2;
7
8  // Utilise transform, pas left !
9  box.style.transform = `translateX(${position}px)`;
10
11  if (position < 500) {
12    requestAnimationFrame(animate);
13  }
14}
15
16requestAnimationFrame(animate);

N'utilise PAS setInterval/setTimeout :

1/* MAUVAIS - setInterval */
2setInterval(() => {
3  position += 2;
4  box.style.left = position + 'px';  /* Pas synchronisé avec l'écran ! */
5}, 16);
6
7/* BON - requestAnimationFrame */
8function animate() {
9  position += 2;
10  box.style.transform = `translateX(${position}px)`;
11  requestAnimationFrame(animate);
12}
13requestAnimationFrame(animate);

Surveillance des performances - comment mesurer les performances

1. Onglet Performance des Chrome DevTools

Étape par étape :

  1. Ouvre les DevTools (F12)
  2. Onglet Performance
  3. Clique sur Record (cercle rouge)
  4. Effectue l'animation
  5. Clique sur Stop
  6. Analyse :
    • FPS - est-ce 60 FPS ?
    • CPU - le thread principal est-il occupé ?
    • Rendering - combien de temps passé en reflow/repaint ?

2. Onglet Rendering - FPS Meter

1DevTools -> ... (More tools) -> Rendering -> Frame Rendering Stats

Affiche :

  • Les FPS en temps réel
  • L'utilisation de la mémoire GPU
  • Le paint flashing (rectangles rouges = repaint)

3. Panneau Layers

1DevTools -> ... (More tools) -> Layers

Affiche :

  • Quels éléments ont leur propre couche GPU
  • Pourquoi la couche a été créée
  • Combien de mémoire elle utilise

4. CSS Stats - surveillance de will-change

1/* Vérifier combien d'éléments ont will-change */
2*[style*="will-change"] {
3  outline: 2px solid red !important;
4}

Exemples d'optimisation - avant et après

Optimisation 1 : effet de survol de carte

1/* AVANT - lent (4 repaints, 2 reflows) */
2.card-before {
3  transition: all 0.3s;
4}
5
6.card-before:hover {
7  width: 320px;         /* Reflow */
8  box-shadow: 0 10px 30px rgba(0,0,0,0.3);  /* Repaint */
9  border: 2px solid gold;  /* Repaint */
10}
11
12/* APRÈS - rapide (0 reflows, 1 composite) */
13.card-after {
14  position: relative;
15  transition: transform 0.3s;
16}
17
18.card-after::before {
19  content: '';
20  position: absolute;
21  inset: -2px;
22  border: 2px solid gold;
23  opacity: 0;
24  transition: opacity 0.3s;
25}
26
27.card-after::after {
28  content: '';
29  position: absolute;
30  inset: 0;
31  box-shadow: 0 10px 30px rgba(0,0,0,0.3);
32  opacity: 0;
33  transition: opacity 0.3s;
34}
35
36.card-after:hover {
37  transform: scale(1.05);  /* GPU */
38}
39
40.card-after:hover::before,
41.card-after:hover::after {
42  opacity: 1;  /* GPU */
43}

Optimisation 2 : spinner de chargement

1/* AVANT - lent (animation de bordure) */
2.spinner-before {
3  border: 4px solid #f3f3f3;
4  border-top: 4px solid #3498db;
5  border-radius: 50%;
6  animation: spin-slow 1s linear infinite;
7}
8
9@keyframes spin-slow {
10  to {
11    transform: rotate(360deg);
12    border-top-color: #e74c3c;  /* Animation de couleur = repaint ! */
13  }
14}
15
16/* APRÈS - rapide (uniquement transform) */
17.spinner-after {
18  border: 4px solid #f3f3f3;
19  border-top: 4px solid #3498db;
20  border-radius: 50%;
21  will-change: transform;
22  animation: spin-fast 1s linear infinite;
23}
24
25@keyframes spin-fast {
26  to {
27    transform: rotate(360deg);  /* GPU uniquement ! */
28  }
29}

Optimisation 3 : apparition fluide au scroll

1/* AVANT - lent (animation de top + opacity) */
2.reveal-before {
3  position: relative;
4  top: 50px;
5  opacity: 0;
6  transition: top 0.6s, opacity 0.6s;
7}
8
9.reveal-before.visible {
10  top: 0;       /* Reflow */
11  opacity: 1;   /* GPU */
12}
13
14/* APRÈS - rapide (uniquement transform + opacity) */
15.reveal-after {
16  transform: translateY(50px);
17  opacity: 0;
18  will-change: transform, opacity;
19  transition: transform 0.6s, opacity 0.6s;
20}
21
22.reveal-after.visible {
23  transform: translateY(0);  /* GPU */
24  opacity: 1;                /* GPU */
25}

Liste de vérification de la performance des animations

Propriétés :

  • J'anime uniquement transform et opacity
  • J'évite width, height, margin, padding
  • J'évite left, top, right, bottom
  • J'évite box-shadow (ou je l'anime via un pseudo-élément)

Accélération GPU :

  • J'utilise transform au lieu du positionnement
  • J'utilise scale au lieu de width/height
  • J'ajoute will-change pour les éléments fréquemment animés
  • Je retire will-change après l'animation

Optimisation :

  • Je préfère les animations CSS au JavaScript
  • Si JS - j'utilise requestAnimationFrame, pas setInterval
  • Je teste dans l'onglet Performance des Chrome DevTools
  • Je vérifie le FPS Meter (objectif : 60 FPS)
  • Je vérifie le panneau Layers (utilisation mémoire)

Mobile :

  • Je teste sur de vrais appareils mobiles
  • Je limite le nombre d'animations simultanées
  • Je désactive les animations pour prefers-reduced-motion

Prefers-reduced-motion - accessibilité

Certains utilisateurs ont activé le réglage « Réduire les animations » dans leur système (vertiges, épilepsie). Respectons cela :

1/* Animations normales */
2.box {
3  transition: transform 0.3s;
4}
5
6.box:hover {
7  transform: scale(1.2) rotate(5deg);
8}
9
10/* Désactiver pour les utilisateurs avec prefers-reduced-motion */
11@media (prefers-reduced-motion: reduce) {
12  .box {
13    transition: none;  /* Aucune animation */
14  }
15
16  .box:hover {
17    transform: scale(1.05);  /* Seulement un changement subtil */
18  }
19}
20
21/* Ou globalement */
22@media (prefers-reduced-motion: reduce) {
23  *,
24  *::before,
25  *::after {
26    animation-duration: 0.01ms !important;
27    animation-iteration-count: 1 !important;
28    transition-duration: 0.01ms !important;
29  }
30}

Récapitulatif

Accéléré par le GPU (RAPIDE) :

  • transform (translate, scale, rotate, skew)
  • opacity
  • filter (blur, brightness - mais plus lent que transform)

Lié au CPU (LENT) :

  • width, height, margin, padding
  • left, top, right, bottom
  • font-size, border-width
  • box-shadow (très lent !)
  • background-color, color (repaint)

will-change :

  • À utiliser avec parcimonie (1-2 propriétés)
  • Ajouter avant l'animation, retirer après
  • Ne pas laisser en permanence (fuite de mémoire !)

Modèles performants :

  • Utilise scale au lieu de width/height
  • Utilise translateX/Y au lieu de left/top
  • Utilise opacity au lieu de visibility/display
  • Anime les ombres via des pseudo-éléments avec opacity

Tests :

  • Onglet Performance des Chrome DevTools
  • FPS Meter (60 FPS = objectif)
  • Panneau Layers (mémoire GPU)
  • Tester sur des appareils mobiles

Souviens-toi : des animations efficaces, c'est comme un chantier de pyramide bien organisé - utilise les bons outils (GPU = leviers) plutôt que la force brute (CPU = levage à la main), et ta page sera aussi fluide que le sable du désert !

Code de cette leçon : style.css
1* {
2  margin: 0;
3  padding: 0;
4  box-sizing: border-box;
5}
6
7body {
8  font-family: 'Segoe UI', sans-serif;
9  background: linear-gradient(180deg, #ff6b35, #ff9a56, #ffd700);
10  min-height: 100vh;
11  display: flex;
12  justify-content: center;
13  align-items: center;
14  color: white;
15}
16
17.scene {
18  text-align: center;
19  padding: 20px;
20}
21
22h2 {
23  margin-bottom: 30px;
24  color: #3d2914;
25}
26
27.sun-container {
28  position: relative;
29  width: 300px;
30  height: 300px;
31  margin: 0 auto;
32}
33
34.sun {
35  font-size: 80px;
36  position: absolute;
37  top: 50%;
38  left: 50%;
39  transform: translate(-50%, -50%);
40  /* Ajoute l'animation pulseSun */
41  /* animation: pulseSun 2s ease-in-out infinite; */
42}
43
44.ray {
45  position: absolute;
46  width: 4px;
47  height: 60px;
48  background: linear-gradient(180deg, #ffd700, transparent);
49  top: 50%;
50  left: 50%;
51  transform-origin: center bottom;
52  opacity: 0;
53  /* Ajoute l'animation rayGlow */
54  /* animation: rayGlow 3s ease-in-out infinite; */
55}
56
57.ray-1 { transform: translate(-50%, -100%) rotate(0deg); }
58.ray-2 { transform: translate(-50%, -100%) rotate(90deg); }
59.ray-3 { transform: translate(-50%, -100%) rotate(180deg); }
60.ray-4 { transform: translate(-50%, -100%) rotate(270deg); }
61
62/* Ajoute différents animation-delay */
63/* .ray-1 { animation-delay: 0s; } */
64/* .ray-2 { animation-delay: 0.5s; } */
65/* .ray-3 { animation-delay: 1s; } */
66/* .ray-4 { animation-delay: 1.5s; } */
67
68/* Définis @keyframes pulseSun */
69/* @keyframes pulseSun { */
70/*   0%, 100% { transform: translate(-50%, -50%) scale(1); } */
71/*   50% { transform: translate(-50%, -50%) scale(1.2); } */
72/* } */
73
74/* Définis @keyframes rayGlow */
75/* @keyframes rayGlow { */
76/*   0% { opacity: 0; height: 30px; } */
77/*   50% { opacity: 1; height: 80px; } */
78/*   100% { opacity: 0; height: 30px; } */
79/* } */
80
81.hint {
82  margin-top: 30px;
83  color: #3d2914;
84  font-style: italic;
85}

Tu as repéré une erreur dans cette leçon ?

Teste tes connaissances

Réponds aux questions de cette leçon. Choisis une réponse pour voir tout de suite si elle est correcte.

  1. 1. Quelles propriétés CSS sont accélérées par le GPU et les plus adaptées à des animations fluides ?

  2. 2. Quelle valeur d'images par seconde (FPS) correspond à une animation fluide ?

Voici 2 des 4 questions de cette leçon. Réponds aux autres dans le jeu.

Tâches pratiques dans le jeu

  • Tri vertical

    Classe ces façons d'animer, de la plus performante (GPU) à la plus lente (reflow) :

Articles utiles