Niveau 28 — Performance

La Performance CSS

Le CSS est l'un des facteurs les plus critiques pour la performance web. Des sélecteurs aux animations, du chargement des polices à la stabilité du layout, optimisez chaque pixel pour une expérience fluide.

1. Comprendre la performance CSS

En une phrase : La performance CSS consiste à minimiser le temps de calcul, de rendu et de peinture par le navigateur, pour atteindre 60 images par seconde (60fps) et un rendu en moins de 100ms.

Chaque propriété CSS déclenche une chaîne de traitement dans le navigateur : Recalculation → Layout → Paint → Composite. Comprendre cette chaîne permet d'écrire du CSS qui ne ralentit jamais l'interface.

PIPELINE DE RENDU CSS
═════════════════════

  DOM + CSSOM
      │
      ▼
  Style Calculation    ← Résolution de la cascade, héritage
      │
      ▼
  Layout (Reflow)      ← Calcul de la taille et position de TOUS les éléments
      │
      ▼
  Paint                ← Dessin des pixels (texte, couleurs, images, ombres)
      │
      ▼
  Composite            ← Assemblage des couches (GPU) et affichage final

  ⚠️ Layout + Paint = COÛTEUX (eviter d'y toucher souvent)
  ✅ Composite seul = PERFORMANT (transform, opacity)

  Objectif : 60fps = 16ms par frame
  Budget par frame :
  ├── JavaScript:  ~8ms
  ├── Style:       ~2ms
  ├── Layout:      ~2ms
  ├── Paint:       ~2ms
  └── Composite:   ~2ms

Pipeline de rendu du navigateur et budget par frame

Métriques de performance

60fps
Fluidité cible
60 images/seconde = 16ms/frame
<100ms
Interactivité
Délai perçu instantané
<16ms
Budget/frame
Pour rester fluide
0
CLS
Cumulative Layout Shift
Outils de mesure : Chrome DevTools (Performance panel), Lighthouse, WebPageTest, Core Web Vitals (LCP, FID, CLS).

2. Syntaxe détaillée

2.1 Sélecteurs performants

Définition : Le navigateur évalue les sélecteurs de droite à gauche. Un sélecteur complexe ou descendant dans un gros DOM peut être très coûteux car il doit vérifier chaque candidat.
/* HIÉRARCHIE DE PERFORMANCE DES SÉLECTEURS */

/* ✅ RAPIDE — sélecteur de classe simple */
.mon-bouton { }

/* ✅ RAPIDE — ID */
#header { }

/* ✅ RAPIDE — sélecteur d'élément direct */
.nav > ul { }

/* ⚠️ MOYEN — descendant */
.nav ul li a { }

/* ⚠️ MOYEN — sélecteur d'attribut */
[data-role="button"] { }

/* ❌ LENT — * sur un gros DOM */
* { }

/* ❌ LENT — deeply nested descendant */
body > div > main > section > article > p > a { }

/* ❌ LENT — sélecteur universel avec descendant */
* > * { }

/* ❌ LENT — pseudo-classe complexe */
:nth-last-child(odd):not(:first-child) { }

/* Règle d'or : garder les sélecteurs courts et ciblés */
/* Le premier élément du sélecteur doit être le plus spécifique */
.bouton-primaire { }        /* ✅ 1 classe = rapide */
nav .bouton-primaire { }    /* ⚠️ descendant */
nav > ul > li > .bouton { } /* ⚠️ 4 niveaux */

✅ Sélecteurs rapides

.classe { }
#id { }
.el > .enfant { }
.el .enfant { }
[attribut] { }

❌ Sélecteurs coûteux

* { }
.el > * > * > * { }
.el .a .b .c .d { }
*:not(.spécial) { }
.a:nth-child(2n+1) { }

2.2 Layout thrashing (reflow)

Définition : Le layout thrashing se produit quand le JavaScript lit et écrit alternativement des propriétés de layout, forçant le navigateur à recalculer le layout à chaque opération.
/* Propriétés par catégorie de coût */

/* ✅ COMPOSITE UNIQUEMENT (GPU) — le moins coûteux */
/* Ne déclenchent ni layout ni paint */
transform
opacity
will-change (utilisé correctement)

/* ⚠️ PAINT — coûteux mais pas de layout */
background (couleur, image, gradient)
box-shadow
text-shadow
border-radius
outline
visibility

/* ❌ LAYOUT (reflow) — le plus coûteux */
/* Force le recalcul de la taille/position de TOUS les éléments */
width, height
padding, margin
border-width
top, right, bottom, left
position
display
float
clear
flex-* (flex-direction, etc.)
grid-* (grid-template-columns, etc.)
font-size (change le flux !)
overflow
z-index
Composite (GPU)

transform, opacity

Rapide — ne déclenche pas de reflow

Paint

box-shadow, border-radius, background

Moyen — redessine les pixels

Layout (Reflow)

width, height, margin, padding, position

Lent — recalcule tout le layout

2.3 Performance des animations

Définition : Pour des animations fluides à 60fps, utilisez uniquement transform et opacity. Ces propriétés ne déclenchent que le compositeur (GPU), sans reflow ni paint.
/* ❌ MAUVAIS — animation de layout (coûteux) */
@keyframes bad-anim {
  from { width: 100px; margin-left: 0; top: 0; }
  to   { width: 200px; margin-left: 50px; top: 100px; }
}
/* Déclenche : Layout + Paint à chaque frame = lent */

/* ✅ BON — animation composite (rapide) */
@keyframes good-anim {
  from { transform: translateX(0) scale(1); opacity: 1; }
  to   { transform: translateX(100px) scale(1.2); opacity: 0.5; }
}
/* Déclenche : Composite uniquement = 60fps */

/* ✅ will-change — prévenir le navigateur */
.animé {
  will-change: transform, opacity;
  /* Crée un nouveau calque GPU (layer promotion) */
  /* À UTILISER avec parcimonie */
}

/* ⚠️ Attention au sur-usage de will-change */
/* Chaque will-change = un calque GPU = mémoire */
❌ MAUVAIS — layout animé

Animation de width + left

✅ BON — composite animé

Animation de transform

2.4 CSS critique

Définition : Le CSS critique est le CSS nécessaire au rendu initial de la page (above-the-fold). Il doit être inliné dans le <head> pour éviter un flash de contenu non stylé (FOUC).
<!-- CSS critique : inline dans le head -->
<head>
  <style>
    /* CSS critique (above-the-fold) */
    body { margin: 0; font-family: system-ui; }
    .header { background: #1e293b; color: white; padding: 1rem; }
    .hero { min-height: 60vh; display: grid; place-items: center; }
  </style>

  <!-- CSS non critique : chargé en async -->
  <link rel="preload" href="styles-full.css" as="style"
        onload="this.onload=null;this.rel='stylesheet'">
  <noscript><link rel="stylesheet" href="styles-full.css"></noscript>
</head>

CSS critique (inline)

Styles nécessaires au rendu initial : layout, header, hero, premiers éléments visibles.

<style>
  body { font-family: system-ui; }
  .header { padding: 1rem; }
  .hero { min-height: 60vh; }
</style>

CSS non critique (async)

Styles différés : footer, animations, composants secondaires, mode sombre.

<link rel="preload"
  href="full.css" as="style"
  onload="this.rel='stylesheet'">

2.5 Optimisation de la livraison CSS

/* === MINIFICATION === */
/* Avant : */
.mon-bouton {
  background-color: #2563eb;
  padding-top: 0.5rem;
  padding-bottom: 0.5rem;
}

/* Après (minifié) : */
.mon-bouton{background-color:#2563eb;padding:.5rem}

/* === COMPRESSION === */
/* gzip : réduction de 70-90% en moyenne */
/* Brotli : encore mieux que gzip (15-25% de plus) */
/* Configurer via serveur web ou CDN */

/* === STRATÉGIES DE CHARGEMENT === */

/* 1. CSS critique inline */
<style>/* critical CSS */</style>

/* 2. CSS non critique en async */
<link rel="preload" href="full.css" as="style"
      onload="this.onload=null;this.rel='stylesheet'">

/* 3. HTTP/2 Server Push (si le serveur le supporte) */
/* Push le CSS critique avec la réponse HTML */

/* 4. Preconnect pour les polices externes */
<link rel="preconnect" href="https://fonts.googleapis.com">

/* 5. Preload pour les ressources critiques */
<link rel="preload" href="critical-font.woff2"
      as="font" type="font/woff2" crossorigin>

preload

Charge la ressource immédiatement, en haute priorité. Pour les ressources critiques.

prefetch

Charge la ressource en arrière-plan, basse priorité. Pour les prochaines pages.

preconnect

Établit la connexion TCP + TLS en avance. Pour les domaines tiers.

dns-prefetch

Résout le DNS en avance. Plus léger que preconnect.

2.6 Optimisation du chargement des polices

/* === font-display === */
@font-face {
  font-family: 'MaPolice';
  src: url('font.woff2') format('woff2');
  font-display: swap;      /* Affiche immédiatement, swap si nécessaire */
  /* swap: FOIT → FOUT (flash) mais texte visible rapidement */
}

/* Valeurs de font-display */
/* auto    — comportement par défaut du navigateur */
/* block   — masque le texte 3s max, puis swap */
/* swap    — texte visible immédiatement, swap quand prêt */
/* fallback — masque 100ms, swap 3s max */
/* optional — aucun flash, mais peut ne pas afficher */

/* === Préchargement des polices === */
<link rel="preload" href="font.woff2" as="font"
      type="font/woff2" crossorigin>

/* === Font subsetting (réduire la taille) === */
/* Ne garder que les caractères nécessaires */
/* Utiliser tools como Glyphhanger ou FontSquirrel */
Aa
font-display: swap
Aa
font-display: optional
Aa
font-display: block

2.7 Stabilité du layout (CLS)

Définition : Le Cumulative Layout Shift (CLS) mesure les décalages inattendus du layout. Un bon CLS doit être ≤ 0.1. Les causes principales : images sans dimensions, polices qui changent la taille, contenu injecté dynamiquement.
/* === Images sans dimensions → CLS === */
/* ❌ MAUVAIS — le layout saute quand l'image charge */
img { width: 100%; }

/* ✅ BON — dimensions explicites */
img { width: 100%; aspect-ratio: 16/9; object-fit: cover; }

/* Ou en HTML : */
/* <img width="800" height="450" ...> */

/* === Polices web → CLS === */
/* Le texte peut changer de taille quand la police se charge */
/* Solution : font-display: optional ou swap + size-adjust */

/* === Contenu injecté → CLS === */
/* ❌ Banners, notifications qui poussent le contenu */
/* ✅ Réserver l'espace avec min-height ou space toggles */

/* === Espace réservé pour vidéo/iframe === */
.video-wrapper {
  aspect-ratio: 16 / 9;
  width: 100%;
}

/* === font-display: optional === */
/* Pas de changement de taille = pas de CLS */
@font-face {
  font-family: 'MaPolice';
  src: url('font.woff2') format('woff2');
  font-display: optional;
}

❌ Sans dimensions

Image sans aspect-ratio

Le layout saute quand l'image charge

✅ Avec dimensions

Image avec aspect-ratio: 16/9

L'espace est réservé, pas de CLS

2.8 content-visibility et CSS containment

/* === content-visibility: auto === */
/* Skip le rendu des éléments hors viewport */
.section-lointaine {
  content-visibility: auto;
  contain-intrinsic-size: 0 500px; /* taille estimée */
}

/* Bénéfice : le navigateur saute le layout/paint des sections hors écran */
/* Réduit le temps de rendu initial de 50%+ sur les longues pages */

/* === contain (CSS Containment) === */
.conteneur-card {
  contain: layout style;  /* Isole le layout de cet élément */
  /* Le navigateur sait que les changements internes
     n'affectent pas le reste de la page */
}

/* Valeurs de contain */
/* layout  — le layout de l'élément n'affecte pas les autres */
/* style   — les compteurs et les quotes sont isolés */
/* paint   — le contenu est peint dans la boîte de l'élément */
/* size    — la taille est indépendante des enfants */
/* content — layout + style + paint (combinaison complète) */
/* strict  — layout + style + paint + size */
/* paint   — le contenu est clippé à la boîte de bordure */

contain: layout

Isole le layout — les changements internes ne reflow pas le reste.

contain: style

Isole les compteurs CSS et les quotes.

contain: paint

Clip le contenu, crée un nouveau stacking context.

3. Visualisation

3.1 Résumé du pipeline de rendu

COUT PAR PROPRIÉTÉ CSS
═══════════════════════

  COMPOSITE (GPU)        PAINT              LAYOUT (Reflow)
  ──────────────         ─────              ───────────────
  transform              background-*       width / height
  opacity                box-shadow         padding / margin
  will-change            text-shadow        border-width
  filter (parfois)       border-radius      position / display
                         outline            top / right / bottom / left
                         visibility         overflow / float / clear
                         color              flex-* / grid-*
                         background-image   font-size (impacte le flux)
                                            line-height (impacte le flux)

  💚 RAPIDE              🟡 MOYEN           🔴 LENT
  GPU uniquement         CPU (repaint)      CPU (reflow global)

  RÈGLE : Pour les animations, utilisez uniquement
          transform et opacity = 60fps garanti

Classification des propriétés CSS par coût de rendu

3.2 Avant / Après : Optimisation

❌ Avant optimisation

/* Sélecteurs lourds */
body > div > main > section > article > p {
  font-size: 16px;
}

/* Animation coûteuse */
@keyframes move {
  from { left: 0; width: 100px; }
  to { left: 500px; width: 200px; }
}

/* CSS non critique inline */
<link rel="stylesheet" href="huge.css">

/* Image sans dimensions */
img { width: 100%; }

/* Font sans font-display */
@font-face {
  font-family: 'Web';
  src: url('web.woff2');
}

✅ Après optimisation

/* Sélecteur ciblé */
.article-text {
  font-size: 1rem;
}

/* Animation performante */
@keyframes move {
  from { transform: translateX(0); }
  to { transform: translateX(500px); }
}

/* CSS critique inline + async */
<style> /* critical */ </style>
<link rel="preload" href="full.css" as="style">

/* Image avec dimensions */
img { width: 100%; aspect-ratio: 16/9; }

/* Font avec font-display */
@font-face {
  font-family: 'Web';
  src: url('web.woff2');
  font-display: swap;
}

4. Exemples progressifs

4.1 Animation performante : avant/après

/* ❌ AVANT — animation de layout (reflow à chaque frame) */
@keyframes slide-bad {
  from { left: 0; width: 100px; }
  to   { left: 500px; width: 200px; }
}
.element-animé {
  position: absolute;
  animation: slide-bad 2s linear infinite;
}

/* ✅ APRÈS — animation composite (60fps) */
@keyframes slide-good {
  from { transform: translateX(0) scaleX(1); }
  to   { transform: translateX(500px) scaleX(2); }
}
.element-animé {
  will-change: transform;
  animation: slide-good 2s linear infinite;
}

4.2 Critical CSS inline

<!-- Étape 1 : Identifier le CSS critique -->
<!-- Utiliser Chrome DevTools → Coverage -->

<!-- Étape 2 : Inline le CSS critique -->
<head>
  <style>
    /* CSS critique = above-the-fold */
    *, *::before, *::after { box-sizing: border-box; margin: 0; }
    body { font-family: system-ui, sans-serif; line-height: 1.6; }
    .header { position: sticky; top: 0; z-index: 100; background: white; }
    .hero { min-height: 60vh; display: grid; place-items: center; }
    .nav { display: flex; gap: 1rem; padding: 1rem; }
  </style>

  <!-- Étape 3 : Charge le CSS non critique en async -->
  <link rel="preload" href="styles-full.css" as="style"
        onload="this.onload=null;this.rel='stylesheet'">
  <noscript>
    <link rel="stylesheet" href="styles-full.css">
  </noscript>
</head>

4.3 Animation performante complète

/* Template d'animation performante */
.animé {
  /* 1. Prévenir le navigateur */
  will-change: transform, opacity;
}

.animé.actif {
  /* 2. Animation sur transform/opacity uniquement */
  animation: fade-slide-in 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94) forwards;
}

@keyframes fade-slide-in {
  from {
    opacity: 0;
    transform: translateY(20px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

/* 3. Respecter prefers-reduced-motion */
@media (prefers-reduced-motion: reduce) {
  .animé.actif {
    animation: none;
    opacity: 1;
    transform: none;
  }
}

/* 4. Nettoyer après l'animation */
.animé.finalisé {
  will-change: auto;  /* Libérer le calque GPU */
}

4.4 Layout stabilisé (zéro CLS)

/* Images avec dimensions explicites */
.article img {
  width: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  background: #f1f5f9; /* Placeholder pendant le chargement */
}

/* Polices avec optional (pas de CLS) */
@font-face {
  font-family: 'SiteFont';
  src: url('font.woff2') format('woff2');
  font-display: optional;
  size-adjust: 102%; /* Ajuster pour matcher la fallback */
  ascent-override: 90%;
  descent-override: 20%;
  line-gap-override: 0%;
}

/* Espacement réservé pour contenus dynamiques */
.notification-zone {
  min-height: 60px; /* Espace toujours réservé */
}

/* Aspect ratio pour vidéo/iframe */
.video-responsive {
  aspect-ratio: 16 / 9;
  width: 100%;
}

/* Contenu invisible jusqu'au chargement */
.contenu {
  visibility: hidden;
}

.contenu.charge {
  visibility: visible;
}

4.5 CSS containment complet

/* Conteneur de carte — isolé du layout global */
.card {
  contain: layout style;
  /* Le layout interne ne reflow pas les autres cartes */
}

/* Liste longue — optimisation progressive */
.liste-longue > .item:nth-child(n+20) {
  content-visibility: auto;
  contain-intrinsic-size: 0 80px; /* hauteur estimée */
}

/* Sidebar — isolée */
.sidebar {
  contain: layout;
  /* Les changements de la sidebar ne reflow pas le contenu */
}

/* Section hors écran */
.section-footer {
  content-visibility: auto;
  contain-intrinsic-size: auto 400px;
}

4.6 Optimisation des sélecteurs

/* ❌ AVANT — sélecteurs complexes */
body > div.container > main#content > section.articles > article > h2.title { }
nav ul li a.active { }
*:not(.special):not(.hidden) { }

/* ✅ APRÈS — sélecteurs ciblés */
.article-title { }
.nav-link.active { }
.interactive { }

/* Règles d'optimisation des sélecteurs :
   1. Utiliser des classes plutôt que des éléments
   2. Éviter les sélecteurs universels (*)
   3. Limiter la profondeur à 3 niveaux max
   4. Le sélecteur le plus spécifique en premier
   5. Éviter :nth-child sur de gros DOMs */

5. Erreurs fréquentes

Erreur 1 : Utiliser le sélecteur * partout
/* FAUX — évalué pour CHAQUE élément du DOM */
* {
  box-sizing: border-box;
  margin: 0;
}

/* CORRECT — utiliser le sélecteur universel une seule fois */
*, *::before, *::after {
  box-sizing: border-box;
}
body, h1, h2, h3, p {
  margin: 0;
}
Erreur 2 : Animer width, height, top, left
/* FAUX — layout reflow à chaque frame */
@keyframes move {
  from { left: 0; width: 100px; }
  to { left: 500px; width: 200px; }
}

/* CORRECT — transform uniquement */
@keyframes move {
  from { transform: translateX(0) scaleX(1); }
  to { transform: translateX(500px) scaleX(2); }
}
Erreur 3 : will-change sur tout
/* FAUX — trop de calques GPU = mémoire gaspillée */
* { will-change: transform; }
.card { will-change: opacity; }
.text { will-change: color; }

/* CORRECT — uniquement sur les éléments animés */
.animé { will-change: transform; }
/* Et retirer après l'animation */
.animé.finalisé { will-change: auto; }
Erreur 4 : Images sans dimensions → CLS
/* FAUX — le layout saute quand l'image charge */
img { width: 100%; }
iframe { width: 100%; }

/* CORRECT — dimensions explicites */
img { width: 100%; aspect-ratio: 16/9; }
iframe { width: 100%; aspect-ratio: 16/9; }
/* Ou en HTML : width="800" height="450" */
Erreur 5 : Charger tout le CSS en sync
/* FAUX — bloque le rendu */
<link rel="stylesheet" href="huge.css">

/* CORRECT — CSS critique inline + async */
<style> /* critical CSS */ </style>
<link rel="preload" href="full.css" as="style"
      onload="this.onload=null;this.rel='stylesheet'">
Erreur 6 : Polices sans font-display
/* FAUX — FOIT (Flash of Invisible Text) */
@font-face {
  font-family: 'Web';
  src: url('web.woff2');
  /* Pas de font-display = behavior par défaut (block) */
}

/* CORRECT — swap pour un texte visible immédiatement */
@font-face {
  font-family: 'Web';
  src: url('web.woff2') format('woff2');
  font-display: swap;
}
Erreur 7 : Sélecteurs deeply nested
/* FAUX — le navigateur doit vérifier chaque niveau */
body > div.container > main > section > article > p > strong { }

/* CORRECT — cibler directement */
.article-highlight strong { }
Erreur 8 : Ne pas utiliser content-visibility
/* AVANT — rendu de tout le contenu même hors viewport */
.longue-page section {
  /* Pas d'optimisation */
}

/* APRÈS — rendu sélectif */
.longue-page section:nth-child(n+5) {
  content-visibility: auto;
  contain-intrinsic-size: auto 500px;
}
/* Réduit le rendu initial de 50%+ */

6. Bonnes pratiques

Les règles d'or de la performance CSS :
  • Animer uniquement transform + opacity pour des animations à 60fps.
  • Utiliser des sélecteurs courts — classes > éléments > descendants.
  • Inline le CSS critique, charger le reste en async.
  • Dimensionner les images/vidéos pour éviter le CLS.
  • Utiliser font-display: swap pour un texte visible immédiatement.
  • Précharger les polices critiques avec <link rel="preload">.
  • Utiliser content-visibility: auto pour les sections hors viewport.
  • Compresser CSS avec gzip/brotli en production.

Checklist de performance CSS

Template d'animation performante :
/* 1. Sélecteur ciblé */
.mon-élément {
  /* 2. will-change pour prévenir */
  will-change: transform, opacity;
}

/* 3. Animation composite uniquement */
.mon-élément.actif {
  animation: my-anim 0.3s ease forwards;
}

@keyframes my-anim {
  from { transform: translateY(20px); opacity: 0; }
  to { transform: translateY(0); opacity: 1; }
}

/* 4. Respecter les préférences */
@media (prefers-reduced-motion: reduce) {
  .mon-élément.actif { animation: none; }
}

/* 5. Nettoyer après */
.mon-élément.finalisé { will-change: auto; }

7. Mini-défi

Défi 1 : Optimiser une animation

Transformez cette animation coûteuse en une version performante utilisant uniquement transform et opacity.

/* ❌ AVANT — coûteux */
@keyframes slide {
  from { left: 0; width: 100px; margin: 0; }
  to   { left: 500px; width: 200px; margin: 20px; }
}

/* ✅ APRÈS — performant */
@keyframes slide-performant {
  from { transform: translateX(0) scaleX(1); }
  to   { transform: translateX(500px) scaleX(2); }
}

.bloc {
  position: relative;
  will-change: transform;
  animation: slide-performant 2s ease-in-out infinite alternate;
}

Défi 2 : Zéro CLS

Corrigez le code suivant pour éliminer tout Cumulative Layout Shift.

/* ❌ AVANT */
img { width: 100%; }
.video { width: 100%; }

/* ✅ APRÈS */
img {
  width: 100%;
  aspect-ratio: 16/9;
  object-fit: cover;
  background: #f1f5f9;
}

.video {
  width: 100%;
  aspect-ratio: 16/9;
}

/* Espacement réservé */
.dynamic-zone {
  min-height: 100px;
}

Défi 3 : CSS critique

Séparez le CSS en critique (inline) et non critique (async).

<head>
  <style>
    /* CSS critique = ce qui est visible au chargement */
    body { margin: 0; font-family: system-ui; }
    .header { padding: 1rem; background: white; position: sticky; top: 0; }
    .hero { min-height: 60vh; display: grid; place-items: center; }
  </style>

  <link rel="preload" href="full.css" as="style"
        onload="this.onload=null;this.rel='stylesheet'">
  <noscript><link rel="stylesheet" href="full.css"></noscript>
</head>

Défi 4 : Sélecteurs optimisés

Refactorisez ces sélecteurs pour améliorer la performance.

/* ❌ AVANT */
body > div.container > main > section > article > h2.title { }
nav ul li a.active { }
*:not(.special):not(.hidden):not(.disabled) { }

/* ✅ APRÈS */
.article-title { }
.nav-link.active { }
.interactive { }

Défi 5 : Conteneur optimisé

Appliquez CSS containment à une liste de cartes pour isoler chaque carte du layout global.

.card {
  contain: layout style;
  /* Le layout interne de chaque carte est isolé */
}

/* Contenu hors écran */
.card:nth-child(n+20) {
  content-visibility: auto;
  contain-intrinsic-size: 0 200px;
}

/* Résultat : le reflow d'une carte n'affecte pas les autres
   et le rendu initial est beaucoup plus rapide */

8. Projets pratiques

Projet 1 : Page optimisée

Optimisez une page de 50+ sections avec :

  • CSS critique inline (header + hero)
  • CSS non critique en async (reste)
  • content-visibility sur les sections hors viewport
  • Images avec aspect-ratio
  • Polices avec font-display: swap + preload
  • Animations en transform/opacity uniquement

Projet 2 : Système d'animations performant

Créez un système d'animations avec :

  • Toutes les animations en transform + opacity
  • will-change géré dynamiquement
  • prefers-reduced-motion respecté
  • Animation library avec utility classes
  • Métriques de performance (fps meter)

Projet 3 : Audit de performance CSS

Audittez un site existant avec :

  • Analyse des sélecteurs (outils de couverture)
  • Identification des animations coûteuses
  • Measurement du CLS
  • Optimization du CSS critique
  • Rapport avant/après avec métriques

9. Révision

Récapitulatif des concepts clés

Concept Propriété / Méthode Impact
Animation performante transform + opacity Composite GPU uniquement = 60fps
Prévenir le navigateur will-change Layer promotion (utiliser avec parcimonie)
Sélecteur rapide .classe O(1) lookup par le navigateur
Sélecteur lent * > * > * Vérification de chaque candidat
CSS critique <style> inline Élimine le FOUC
CSS async rel="preload" Ne bloque pas le rendu
Font perf font-display: swap Texte visible immédiatement
Stabilité layout aspect-ratio Zéro CLS pour images/vidéos
Containment contain Isole le layout d'un élément
Rendu différé content-visibility: auto Saute le rendu hors viewport
Compression brotli / gzip Réduction de 70-90% de la taille
Préchargement <link rel="preload"> Charge en haute priorité

Quiz rapide

  1. Quelles sont les 2 propriétés CSS à utiliser pour des animations performantes ?
  2. Qu'est-ce que le layout thrashing et comment l'éviter ?
  3. Qu'est-ce que le CLS et quel est le score idéal ?
  4. Comment charger le CSS non critique sans bloquer le rendu ?
  5. Quelle valeur de font-display permet un texte visible immédiatement ?
  6. Qu'est-ce que will-change et pourquoi ne pas en abuser ?
  7. Comment isoler le layout d'un composant ?
  8. Quelles propriétés CSS déclenchent un layout reflow ?
  9. Comment optimiser le rendu d'une longue page ?
  10. Quelle est la différence entre preload et prefetch ?
Réponses :
  1. transform et opacity — elles n'activent que le compositeur GPU.
  2. Le layout thrashing = lecture/écriture alternée de propriétés de layout en JS. L'éviter en batch les lectures et les écritures séparément.
  3. CLS = Cumulative Layout Shift, score idéal ≤ 0.1. Causé par des images sans dimensions, des polices qui changent la taille, du contenu injecté.
  4. <link rel="preload" href="..." as="style" onload="this.rel='stylesheet'">
  5. font-display: swap — affiche le texte avec la fallback immédiatement, swap quand la police est prête.
  6. will-change crée un calque GPU dédié. En abuser = trop de calques = consommation mémoire excessive.
  7. Avec contain: layout style ou contain: strict.
  8. width, height, padding, margin, position, display, top/right/bottom/left, flex-*, grid-*, font-size, overflow.
  9. Avec content-visibility: auto sur les sections hors viewport.
  10. preload = haute priorité, pour les ressources critiques. prefetch = basse priorité, pour les prochaines navigations.

Cheat sheet rapide

/* === ANIMATION PERFORMANTE === */
.el { will-change: transform, opacity; }
.el.animé { animation: slide 0.3s ease forwards; }
@keyframes slide { from { transform: translateY(20px); opacity: 0; } to { transform: none; opacity: 1; } }

/* === SÉLECTEURS RAPIDES === */
.mon-el { }              /* ✅ classe simple */
#mon-id { }              /* ✅ ID */
.el > .enfant { }        /* ✅ enfant direct */
.el .petit { }           /* ⚠️ descendant (acceptable) */

/* === CSS CRITIQUE INLINE === */
<style>/* above-the-fold CSS */</style>
<link rel="preload" href="full.css" as="style" onload="this.rel='stylesheet'">

/* === FONTS === */
@font-face { font-display: swap; }

/* === CLS === */
img, video, iframe { width: 100%; aspect-ratio: 16/9; }

/* === CONTAINMENT === */
.card { contain: layout style; }
.section:nth-child(n+10) { content-visibility: auto; contain-intrinsic-size: auto 500px; }

/* === PREFERS === */
@media (prefers-reduced-motion: reduce) { * { animation: none !important; } }