Layout Shift causé par les transitions CSS

Apprenez à trouver et supprimer les transitions CSS qui provoquent des layout shifts

Arjen Karel Core Web Vitals Consultant
Arjen Karel - linkedin
Last update: 2026-10-17

Pourquoi les transitions CSS provoquent des layout shifts

Les Cumulative Layout Shifts causés par des transitions CSS surviennent généralement tôt, pendant la phase de chargement de la page. Une transition sur width ou height anime le changement de taille quand une feuille de style tardive ou un script modifie le style, et le contenu situé en dessous se déplace avec. Ces shifts ne se produisent pas à chaque page vue. Cela les rend difficiles à déboguer.

Dernière révision par Arjen Karel en mars 2026

Le problème de transition: all

La cause que je retrouve le plus souvent est transition: all. Elle est partout. D'après le chapitre CSS du Web Almanac 2022, 53 % des pages l'utilisent, plus que toute autre propriété de transition. width et height s'animent donc aussi, et déplacent le contenu autour de l'élément.

Une transition CSS comporte quatre éléments : la propriété, la durée, la timing function et le délai. En notation abrégée :

/* property | duration | timing-function | delay */
transition: margin-right 4s ease-in-out 1s;

Le problème commence avec transition: all .3s ease sur un élément où le développeur voulait simplement un fondu sur la couleur au survol.

Une transition ne s'exécute jamais sur le premier style calculé par le navigateur pour un élément. Elle ne se déclenche que si ce style change plus tard. Et pendant le chargement de la page, cela arrive souvent : une feuille de style qui arrive tard, ou un script qui ajoute une classe au menu de navigation. Avec transition: all, le navigateur anime aussi le changement de taille. Le menu au-dessus de la ligne de flottaison met donc 300 millisecondes à prendre sa place et repousse tout ce qui est en dessous. Un layout shift qui ne se produit que pendant le chargement. Bon courage pour le reproduire.

Quelles propriétés CSS provoquent des layout shifts ?

Toutes les transitions CSS ne provoquent pas de layout shifts. Seule une propriété qui modifie la taille ou la position d'un élément oblige le navigateur à recalculer le layout, et seul le layout déplace le contenu autour. D'après les recherches de Google, les pages qui animent des propriétés CSS déclenchant du layout ont 15 % de chances en moins de valider le CLS. Margin et border-width font encore pire : les pages qui les animent affichent un CLS médiocre presque deux fois plus souvent que la normale.

Chacune de ces propriétés déclenche un recalcul du layout. Appliquer une transition sur l'une d'entre elles peut donc déplacer l'élément lui-même ou le contenu autour : width, height, margin, padding, top, left, right, bottom, border-width, font-size.

Sans risque pour le CLS : transform et opacity peuvent s'exécuter sur le compositor. Filter et clip-path coûtent tout au plus un repaint. Aucune de ces deux paires ne déclenche de layout.

Paint uniquement, donc également sans risque pour le CLS : background-color, color, box-shadow, outline.

Transform et opacity sont les deux qui comptent. Un transform ne modifie pas la position de l'élément dans le layout ; le CLS ne le compte donc jamais comme un layout shift. Besoin de déplacer un élément ? Utilisez transform: translate() plutôt que top et left. Pour l'agrandir ou le rétrécir, utilisez transform: scale() au lieu de width et height.

Le survol n'est pas une excuse non plus. Le CLS ignore les shifts survenus dans les 500 ms suivant un clic, un tap ou l'appui sur une touche. Mais déplacer la souris sur un élément n'entre pas dans cette catégorie : une transition au survol sur width compte donc intégralement.

La vidéo ci-dessous montre une transition qui décale le layout pendant la phase de chargement. Je vois cela tout le temps. La repérer demande de creuser un peu.

Trouver et corriger les transitions CSS

Pour repérer chaque layout shift causé par une transition CSS, listez d'abord toutes les transitions de la page et vérifiez lesquelles animent une propriété qui affecte le layout. Désactivez ou modifiez celles-ci, puis mesurez si le CLS disparaît.

Astuce Core Web Vitals : ces shifts surviennent tôt dans le chargement de la page, et pas à chaque visite. Émulez un mobile, bridez le réseau et désactivez le cache. Sur un chargement lent, le navigateur affiche la page avant l'arrivée de cette feuille de style ou de ce script tardif : le shift apparaît alors beaucoup plus souvent.

Étape 1 : trouver les transitions CSS

Vous pouvez trouver les transitions CSS à la main : ouvrez chaque feuille de style et cherchez le mot « transition ». Dix minutes, peut-être. Plus rapide : collez ce snippet dans la console et appuyez sur Entrée.

(() => {

  let nodeTable = [];
  let nodeArray = [];

  // Get the name of the node
  function getName(node) {
    const name = node.nodeName;
    return node.nodeType === 1
      ? name.toLowerCase()
      : name.toUpperCase().replace(/^#/, '');
  }

  // Get the selector
  const getSelector = (node) => {
    let sel = '';

    try {
      while (node && node.nodeType !== 9) {
        const el = node;
        const part = el.id
          ? '#' + el.id
          : getName(el) +
          (el.classList &&
            el.classList.value &&
            el.classList.value.trim() &&
            el.classList.value.trim().length
            ? '.' + el.classList.value.trim().replace(/\s+/g, '.')
            : '');
        if (sel.length + part.length > (100) - 1) return sel || part;
        sel = sel ? part + '>' + sel : part;
        if (el.id) break;
        node = el.parentNode;
      }
    } catch (err) {
      // Do nothing...
    }
    return sel;
  };

  const getNodesWithTransition = (node) => {

    // Get the computed style
    let cs = window.getComputedStyle(node);
    let tp = cs['transition-property'];
    let td = cs['transition-duration'];

    // If there is a transition, add it to the table
    if (tp !== '' && tp !== 'none' && td != '0s') {
      nodeTable.push({ selector: getSelector(node), transition: cs['transition'] });
      nodeArray.push(node);
    }

    // Recursively call this function for each child node
    for (let i = 0; i < node.children.length; i++) {
      getNodesWithTransition(node.children[i]);
    }
  }

  // find all transitions
  getNodesWithTransition(document.body);

  // Display the results in the console
  console.log('%cReadable table of selectors and their transitions', 'color: red; font-weight: bold;');
  console.table(nodeTable);

  console.log('%cNodeList for you to inspect (harder to read but more info)', 'color: red; font-weight: bold;');
  console.log(nodeArray);


  // styles to temporarity override the transitions
  let selectors = nodeTable.map((item) => item.selector).join(', ');

  console.log('%cSpecific CSS to disable all transitions on this page', 'color: red; font-weight: bold;');
  console.log(`<style>${selectors}{transition-property: none !important;}</style>`);

  console.log('%cGlobal CSS to disable all transitions on this page (not suggested on production)', 'color: red; font-weight: bold;');
  console.log(`<style>*{transition-property: none !important;}</style>`);

})()

Il affiche un tableau listant chaque transition de la page et l'élément auquel elle s'applique.

Regardez maintenant la colonne des transitions. Commencez par chaque ligne indiquant all : elle couvre toutes les propriétés animables, y compris width et height. Cherchez ensuite les lignes qui mentionnent directement width ou height. Margin et padding déplacent aussi le contenu, tout comme top et left.

Étape 2 : modifier les transitions CSS

Le snippet affiche aussi le CSS pour couper ces transitions. Pour un test rapide, choisissez la simplicité et désactivez chaque transition en une ligne :

<style>*{transition-property: none !important;}</style>

Sur un site en production, il faut un peu plus de finesse. Supprimez uniquement les propriétés de transition inutiles, sélecteur par sélecteur. Par exemple, remplacez #button{transition: all .2s} par #button{transition: background-color .2s}.

Étape 3 : mesurer l'impact sur le layout shift

Dernière étape : mesurer. Mon extension Chrome Core Web Vitals Visualizer montre les shifts sur votre propre écran, et un outil RUM comme CoreDash indique s'ils ont également disparu pour vos véritables visiteurs. Dans CoreDash, vous pouvez décomposer le CLS par élément déplacé. Après modification, ce menu ou cette bannière devrait disparaître de la liste, et le p75 sur mobile devrait suivre.

Bonnes pratiques pour les transitions

  1. Nommez la propriété exacte. Écrivez transition: background-color .2s ease au lieu de transition: all, et une propriété que vous ne comptiez pas animer ne pourra rien décaler.
  2. Animez transform et opacity. Le navigateur peut exécuter les deux sur le compositor et aucune ne déclenche de layout. Utilisez transform: translate() pour déplacer un élément et transform: scale() pour modifier sa taille. Opacity crée un fondu. C'est aussi ce que recommande Google.
  3. Donnez à un élément soumis à une transition une width et une height explicites, ou un aspect-ratio, avant et après la transition, pour que le contenu autour ne bouge pas. Plus de détails dans corriger les layout shifts causés par le redimensionnement automatique des images.
  4. N'abusez pas de will-change. Cette propriété indique au navigateur de se préparer à une animation en promouvant l'élément sur son propre compositor layer. Cela a un coût. Avec will-change: transform, l'élément reçoit un nouveau stacking context et devient le containing block de ses descendants positionnés, et chaque calque supplémentaire consomme de la mémoire GPU. Appliquez-le en JavaScript juste avant le début de l'animation et retirez-le dès qu'elle se termine. Ne le laissez pas dans la feuille de style en permanence.

Beaucoup de sites font encore l'erreur. Le Web Almanac 2025 relève des animations non compositées sur 40 % des pages mobiles. Le CLS est le Core Web Vital avec le meilleur taux de réussite, 81 % sur mobile. Si votre site fait partie des 19 % qui échouent, les transitions CSS sont peut-être la cause que vous n'avez pas encore vérifiée.

About the author

Arjen Karel is a web performance consultant and the creator of CoreDash, a Real User Monitoring platform that tracks Core Web Vitals data across hundreds of sites. He also built the Core Web Vitals Visualizer Chrome extension. He has helped clients achieve passing Core Web Vitals scores on over 925,000 mobile URLs.

Arjen Karel

Besoin d'un site plus rapide ?

Je suis Arjen et je répare des sites lents depuis 17 ans. Si vous rencontrez des problèmes que vous ne pouvez pas résoudre vous-même, envoyez-moi votre URL et parlons-en.

Envoyez-moi votre URL
Arjen Karel
Arjen Karel, Consultant en performance web depuis 2009

Pourquoi notre fix ne fonctionne pas ?

Parfois le CDN sert encore l'ancien HTML, ou le fix a été appliqué sur le template desktop alors que le mobile en utilise un autre. Envoyez-moi votre URL et le problème, et je vous réponds avec quelques créneaux. J'analyse d'abord vos field data, puis nous passons une heure sur votre site, et sous 24 heures vous avez mes notes par écrit. 350 € HT.

Envoyez-moi votre URL

Pas de développeur pour réaliser le fix ? Je fais le travail moi-même, sur une copie de votre site.

Layout Shift causé par les transitions CSS Core Web Vitals Layout Shift causé par les transitions CSS