Comment les animations déclenchées au défilement provoquent du CLS

Votre header masqué au défilement fait peut-être échouer le CLS en silence. Voici pourquoi, et comment le corriger.

Arjen Karel
Mis à jour le · 7 min de lecture
Partager

Quand on me demande d'auditer le Cumulative Layout Shift, je commence évidemment par les décalages pendant la phase de chargement. Je vérifie les images sans dimensions, les publicités injectées, les changements de police (FOUT).

Mais le CLS ne survient pas uniquement pendant le chargement de la page. Il est parfois provoqué par des interactions comme le défilement. Ces décalages sont difficiles à repérer car Lighthouse ne fait pas défiler la page : il faut savoir exactement où chercher.

Lors d'un audit récent, j'ai ajouté les données RUM de CoreDash et j'ai repéré un comportement suspect. Un élément #header semblait causer beaucoup plus de décalages de mise en page que la normale.

Il s'agit en réalité d'un header fixe qui disparaît quand l'utilisateur défile vers le bas et réapparaît quand il remonte. C'est un schéma fréquent. Mal implémenté, il provoque un décalage de mise en page à chaque changement de sens du défilement.

Pourquoi les décalages déclenchés au défilement sont différents

En principe, aucun CLS ne se produit après une interaction. Le CLS mesure les décalages de mise en page inattendus, et après une interaction, on s'attend à ce que l'affichage change. C'est pourquoi la métrique CLS prévoit une période de grâce automatique de 500 ms. Les décalages de mise en page qui surviennent dans les 500 millisecondes suivant une interaction utilisateur sont exclus du score CLS. Pour gérer cela, le navigateur positionne et vérifie un flag hadRecentInput sur ces décalages.

Le problème, c'est que cette règle ne s'applique qu'aux événements ponctuels comme les clics, les appuis tactiles et les frappes au clavier. D'après la spécification Layout Instability, le défilement n'est explicitement pas une interaction excluante. Les glissements et les pincements pour zoomer non plus. Seuls les événements ponctuels bénéficient de la période de grâce de 500 ms.

Chaque décalage de mise en page déclenché par un événement de défilement compte donc dans le score CLS.

D'après le Web Almanac 2025, 40 % des pages mobiles utilisent encore des animations non compositées. Ce sont des animations sur des propriétés comme top, height ou margin qui déclenchent un recalcul de mise en page à chaque frame. Si l'une d'elles s'exécute pendant le défilement, chaque frame sans exception compte dans le CLS.

Le problème du header masqué au défilement

Prenons le cas observé aujourd'hui : le « header masqué au défilement ». Un header fixe utilise JavaScript pour modifier sa visibilité lors du défilement. Voici l'implémentation :

/* CSS */
#header {
  position: fixed;
  top: 0;
  transition: top 0.3s ease;
}

/* JavaScript */
window.addEventListener('scroll', () => {
  if (scrollingDown) {
    header.style.top = '-80px';  // masquer
  } else {
    header.style.top = '0px';    // afficher
  }
});

Le décalage de mise en page provient de la combinaison entre transition: top 0.3s ease et la modification de header.style.top en JavaScript.

Voici pourquoi : top est une propriété de mise en page. Quand le navigateur traite une modification de top, il exécute l'intégralité du pipeline de rendu : Style, Layout, Paint, Composite. À chaque frame d'animation pendant cette transition, le navigateur recalcule la mise en page. Chaque recalcul produit une nouvelle entrée de décalage de mise en page.

À chaque changement de sens de défilement, le compteur de CLS continue de grimper tant que l'utilisateur interagit avec la page.

J'ai créé une démo reproduisant exactement ce comportement à partir d'un site en production que j'auditais, et j'ai enregistré une vidéo. Le CLS total a dépassé 0,1 après quelques défilements. Certes, un tel niveau de défilement arrive rarement dans la réalité, mais le mécanisme ne change pas. Et j'ai déjà vu ce décalage faire basculer des sites au-delà du seuil et faire échouer leur CLS total.

Quelles propriétés CSS peuvent provoquer des décalages de mise en page lorsqu'elles sont animées ?

Pour qu'une animation génère du CLS, elle doit déclencher le Layout. Le pipeline de rendu du navigateur comporte trois étapes principales après le calcul des styles : Layout, Paint et Composite. Les propriétés qui n'affectent que l'étape Composite ne provoquent jamais de décalage de mise en page. Pour comprendre comment les transitions CSS provoquent des décalages de mise en page, consultez l'article dédié.

Propriétés qui déclenchent le layout (et provoquent du CLS lorsqu'elles sont animées) :

top, left, right, bottom, width, height, margin, padding, border-width, font-size

Toutes ces propriétés modifient la géométrie ou la position d'un élément. Le navigateur doit recalculer la position de cet élément (et potentiellement celle d'autres éléments).

Propriétés qui évitent totalement le layout (et sans risque pour les animations) :

transform et opacity s'exécutent sur le thread du compositeur. Elles contournent à la fois le layout et le paint. Le navigateur les traite sur le GPU (voilà pourquoi ces animations fonctionnent même lorsque le main thread est occupé). Comme elles ne déclenchent jamais de recalcul de mise en page, elles ne produisent aucune entrée de décalage.

Remplacer les propriétés de mise en page par des transforms

La solution pour le header masqué au défilement est simple. Remplacez l'animation de top par transform: translateY().

/* CSS - AVANT (provoque du CLS) */
#header {
  position: fixed;
  top: 0;
  transition: top 0.3s ease;
}

/* CSS - APRÈS (sans CLS) */
#header {
  position: fixed;
  top: 0;
  transition: transform 0.3s ease;
}

/* JavaScript - AVANT */
header.style.top = '-80px';

/* JavaScript - APRÈS */
header.style.transform = 'translateY(-80px)';

Le résultat est identique : le header glisse vers le haut et disparaît. Mais transform: translateY() s'exécute entièrement sur le compositeur et ne provoque aucun décalage de mise en page.

Le même principe s'applique à toute animation déclenchée au défilement :

  • Panneaux coulissants : utilisez transform: translateX() au lieu de left ou right
  • Éléments extensibles : utilisez transform: scaleY() au lieu de height
  • Éléments en fondu : utilisez opacity au lieu de visibility combiné à des variations de hauteur

D'autres cas au défilement qui provoquent du CLS

Le header masqué au défilement est fréquent, mais ce n'est pas le seul cas. Tout code JavaScript modifiant une propriété de mise en page lors du défilement produira un décalage de mise en page. Par exemple :

Les effets de parallaxe utilisant top ou margin-top. Remplacez-les par transform: translateY() ou utilisez l'approche CSS avec perspective et translateZ pour une parallaxe en pur CSS.

La navigation sticky qui change de hauteur au défilement. Une barre de navigation qui passe de 80 px à 50 px lorsque l'utilisateur dépasse un certain seuil. Si ce changement de hauteur est animé avec transition: height, remplacez-le par transform: scaleY().

Les barres de progression au défilement utilisant width. Un indicateur de lecture qui s'étend de gauche à droite en animant width. Utilisez plutôt transform: scaleX() avec transform-origin: left.

Alternative moderne : les CSS Scroll-Driven Animations. L'API animation-timeline: scroll() permet de piloter des animations selon la progression du défilement, sans aucun JavaScript. Comme ces animations utilisent par défaut transform et opacity et tournent sur le compositeur, elles ne provoquent jamais de décalage de mise en page. La prise en charge couvre Chrome 115+ et Edge 115+, tandis que Safari est encore en cours de développement. Pour les effets liés au défilement comme la parallaxe, les barres de progression et les apparitions, c'est la solution la plus propre. Si vous voulez abandonner le défilement en JavaScript, l'API scroll-driven animations est la voie à suivre.

Pourquoi Lighthouse ne détecte pas ce problème

Lighthouse simule le chargement d'une page. Il ne fait pas défiler la page. Il n'interagit pas avec la page après son chargement. Les décalages de mise en page déclenchés au défilement sont donc totalement invisibles en lab data.

La seule façon d'intercepter ces décalages est d'utiliser le Real User Monitoring. Les field data capturent l'intégralité du cycle de vie de la page, notamment chaque décalage de mise en page qui survient lorsque l'utilisateur défile, clique et navigue. Si le score CLS dans CrUX est moins bon que celui de Lighthouse, les animations déclenchées au défilement font partie des premières pistes à explorer. CoreDash mesure le CLS des visiteurs réels, y compris chaque décalage provoqué par le défilement, pour vous montrer exactement quels éléments sont en cause.

Comment repérer les décalages de mise en page déclenchés au défilement

Ouvrez les DevTools de Chrome. Allez dans le panneau Performance. Lancez l'enregistrement (l'icône en forme de cercle), puis faites défiler la page de haut en bas à plusieurs reprises. Arrêtez l'enregistrement. Cherchez les losanges violets sur la piste Layout Shifts. Si vous constatez une série de décalages alignée avec vos mouvements de défilement, vous faites face à un problème de CLS lié au défilement. Vous pouvez aussi utiliser l'extension Chrome Core Web Vitals Visualizer pour observer le CLS en temps réel pendant le défilement.

La règle de base

Si un élément bouge au défilement, animez-le avec transform. S'il s'estompe, animez-le avec opacity. Ces propriétés sont gérées uniquement par le compositeur.

Côté JavaScript : vérifiez les gestionnaires d'événements de défilement, les callbacks IntersectionObserver et tout code modifiant les styles d'un élément selon la position dans la page. Si vous y trouvez top, left, margin, width, height ou padding, remplacez-les par l'équivalent avec transform.

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.

Arjen KarelCore Web Vitals : rouge sur mobile ?Envoyez-moi votre URL