Débloquez des interactions web avancées. Ce guide complet explore la synchronisation des timelines d'animation CSS pilotées par le défilement, couvrant view(), scroll() et des techniques pratiques pour créer des expériences utilisateur magnifiques et performantes.
Maîtriser les animations CSS pilotées par le défilement : une plongée au cœur de la synchronisation des timelines
Pendant des années, la création d'animations attrayantes liées au défilement sur le web a été le domaine de JavaScript. Les développeurs se sont appuyés sur des bibliothèques et des boucles complexes de `requestAnimationFrame`, écoutant constamment les événements de défilement. Bien qu'efficace, cette approche s'accompagne souvent d'un coût en termes de performance, entraînant des saccades et une expérience moins fluide, en particulier sur les appareils moins puissants. Aujourd'hui, un changement de paradigme est en cours, déplaçant toute cette catégorie de conception d'interface utilisateur directement dans le moteur de rendu haute performance du navigateur, grâce aux animations CSS pilotées par le défilement.
Cette nouvelle spécification puissante nous permet de lier la progression d'une animation directement à la position de défilement d'un conteneur ou à la visibilité d'un élément. Le résultat est des animations parfaitement fluides, accélérées par le GPU, qui sont déclaratives, accessibles et remarquablement efficaces. Cependant, le véritable potentiel créatif se débloque lorsque nous allons au-delà de l'animation d'éléments uniques et commençons à orchestrer de multiples interactions complexes en harmonie. C'est l'art de la synchronisation d'animation.
Dans ce guide complet, nous explorerons les concepts fondamentaux des timelines d'animation CSS pilotées par le défilement et nous plongerons dans les techniques requises pour les synchroniser. Vous apprendrez à créer des effets de parallaxe en couches, des révélations narratives séquentielles et des interactions de composants complexes — le tout avec du CSS pur. Nous aborderons :
- La différence fondamentale entre les timelines `scroll()` et `view()`.
- Le concept révolutionnaire des timelines nommées pour synchroniser plusieurs éléments.
- Le contrôle affiné de la lecture de l'animation à l'aide de `animation-range`.
- Des exemples pratiques et concrets avec du code que vous pouvez utiliser dès aujourd'hui.
- Les meilleures pratiques en matière de performance, d'accessibilité et de compatibilité des navigateurs.
Préparez-vous à repenser ce qui est possible avec le CSS et à élever vos expériences web à un nouveau niveau d'interactivité et de raffinement.
Les fondations : Comprendre les timelines d'animation
Avant de pouvoir synchroniser des animations, nous devons d'abord comprendre le mécanisme qui les pilote. Traditionnellement, la timeline d'une animation CSS est basée sur le passage du temps, défini par sa `animation-duration`. Avec les animations pilotées par le défilement, nous coupons ce lien avec le temps et connectons plutôt la progression de l'animation à une nouvelle source : une timeline de progression.
Ceci est réalisé principalement grâce à la propriété `animation-timeline`. Au lieu de laisser l'animation se dérouler d'elle-même après avoir été déclenchée, cette propriété indique au navigateur de parcourir les keyframes de l'animation en fonction de la progression d'une timeline spécifiée. Lorsque la timeline est à 0%, l'animation est à son keyframe de 0%. Lorsque la timeline est à 50%, l'animation est à son keyframe de 50%, et ainsi de suite.
La spécification CSS fournit deux fonctions principales pour créer ces timelines de progression :
- `scroll()`: Crée une timeline anonyme qui suit la progression du défilement d'un conteneur de défilement (un scroller).
- `view()`: Crée une timeline anonyme qui suit la visibilité d'un élément spécifique lorsqu'il se déplace dans le viewport (ou tout autre scroller).
Examinons chacune d'entre elles en détail pour construire une base solide.
Plongée en profondeur : La timeline de progression `scroll()`
Qu'est-ce que `scroll()`?
La fonction `scroll()` est idéale pour les animations qui doivent correspondre à la progression globale du défilement d'une page ou d'un élément défilable spécifique. Un exemple classique est une barre de progression de lecture en haut d'un article qui se remplit à mesure que l'utilisateur fait défiler la page vers le bas.
Elle mesure à quel point un utilisateur a fait défiler un conteneur de défilement. Par défaut, elle suit la position de défilement du document entier, mais elle peut être configurée pour suivre n'importe quel conteneur défilable de la page.
Syntaxe et paramètres
La syntaxe de base pour la fonction `scroll()` est la suivante :
animation-timeline: scroll(<scroller> <axis>);
Détaillons ses paramètres :
- `<scroller>` (optionnel) : Spécifie la progression de quel conteneur de défilement doit être suivie.
root: La valeur par défaut. Elle représente le conteneur de défilement du viewport du document (la barre de défilement principale de la page).self: Suit la position de défilement de l'élément lui-même, en supposant qu'il s'agit d'un conteneur de défilement (par exemple, avec `overflow: scroll`).nearest: Suit la position de défilement du conteneur de défilement ancêtre le plus proche.
- `<axis>` (optionnel) : Définit l'axe de défilement à suivre.
block: La valeur par défaut. Suit la progression le long de l'axe de bloc (vertical pour les modes d'écriture horizontaux comme le français).inline: Suit la progression le long de l'axe en ligne (horizontal pour le français).y: Un alias explicite pour l'axe vertical.x: Un alias explicite pour l'axe horizontal.
Exemple pratique : Une barre de progression de défilement de page
Construisons cet indicateur de progression de lecture classique. C'est une démonstration parfaite de `scroll()` dans sa forme la plus simple.
Structure HTML :
<div class="progress-bar"></div>
<article>
<h1>Un long titre d'article</h1>
<p>... beaucoup de contenu ici ...</p>
<p>... plus de contenu pour rendre la page défilable ...</p>
</article>
Implémentation CSS :
/* Définir les keyframes pour la barre de progression */
@keyframes grow-progress {
from { transform: scaleX(0); }
to { transform: scaleX(1); }
}
/* Styler la barre de progression */
.progress-bar {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 8px;
background-color: dodgerblue;
transform-origin: left; /* Animer l'échelle depuis le côté gauche */
/* Lier l'animation à la timeline de défilement */
animation: grow-progress linear;
animation-timeline: scroll(root block);
}
/* Style de base du body pour la démonstration */
body {
font-family: sans-serif;
line-height: 1.6;
padding: 2rem;
height: 300vh; /* S'assurer qu'il y a beaucoup à faire défiler */
}
Explication :
- Nous définissons une animation simple `grow-progress` qui met à l'échelle un élément horizontalement de 0 à 1.
- La `.progress-bar` est fixée en haut du viewport.
- La magie opère avec les deux dernières propriétés. Nous appliquons l'animation `grow-progress`. Point crucial, au lieu de lui donner une durée (comme `1s`), nous définissons son `animation-timeline` sur `scroll(root block)`.
- Cela dit au navigateur : "Ne joue pas cette animation dans le temps. Au lieu de cela, parcours ses keyframes à mesure que l'utilisateur fait défiler le document racine verticalement (l'axe `block`)."
Lorsque l'utilisateur est tout en haut de la page (0% de progression de défilement), le `scaleX` de la barre sera de 0. Lorsqu'il est tout en bas (100% de progression de défilement), son `scaleX` sera de 1. Le résultat est un indicateur de progression parfaitement fluide sans aucun JavaScript requis.
La puissance de la proximité : La timeline de progression `view()`
Qu'est-ce que `view()`?
Alors que `scroll()` concerne la progression globale d'un conteneur, `view()` concerne le parcours d'un seul élément à travers la zone visible d'un scroller. C'est la solution CSS native pour le modèle incroyablement courant de "l'animation à la révélation", où les éléments apparaissent en fondu, glissent vers le haut ou s'animent d'une autre manière lorsqu'ils entrent à l'écran.
La timeline `view()` commence lorsqu'un élément devient visible pour la première fois dans le scrollport et se termine lorsqu'il en est complètement sorti. Cela nous donne une timeline de 0% à 100% qui est directement liée à la visibilité d'un élément, ce qui la rend incroyablement intuitive pour les effets de révélation.
Syntaxe et paramètres
La syntaxe pour `view()` est légèrement différente :
animation-timeline: view(<axis> <view-timeline-inset>);
- `<axis>` (optionnel) : Le même que dans `scroll()` (`block`, `inline`, `y`, `x`). Il détermine par rapport à quel axe du scrollport la visibilité de l'élément est suivie.
- `<view-timeline-inset>` (optionnel) : C'est un paramètre puissant qui vous permet d'ajuster les limites du viewport "actif". Il peut accepter une ou deux valeurs (pour les décalages de début et de fin, respectivement). Vous pouvez utiliser des pourcentages ou des longueurs fixes. Par exemple, `100px 20%` signifie que la timeline considère que le viewport commence à 100px du haut et se termine à 20% du bas. Cela permet d'ajuster finement quand l'animation commence et se termine par rapport à la position de l'élément à l'écran.
Exemple pratique : Apparition en fondu à la révélation
Créons un effet classique où des cartes de contenu apparaissent en fondu et glissent en place lorsqu'elles défilent à l'écran.
Structure HTML :
<section class="content-grid">
<div class="card">Carte 1</div>
<div class="card">Carte 2</div>
<div class="card">Carte 3</div>
<div class="card">Carte 4</div>
</section>
Implémentation CSS :
/* Définir les keyframes pour l'animation de révélation */
@keyframes fade-in-up {
from {
opacity: 0;
transform: translateY(50px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.card {
/* Appliquer l'animation à chaque carte */
animation: fade-in-up linear;
animation-timeline: view(); /* Et voilà ! */
/* Autre style */
background-color: #f0f0f0;
padding: 2rem;
border-radius: 8px;
min-height: 200px;
display: grid;
place-content: center;
font-size: 2rem;
}
/* Style de la mise en page */
.content-grid {
display: grid;
gap: 2rem;
padding: 10vh 2rem;
}
Explication :
- Les keyframes `fade-in-up` définissent l'animation que nous souhaitons : commencer transparent et légèrement plus bas, finir opaque et à sa position finale.
- Chaque élément `.card` reçoit cette animation.
- La ligne cruciale est `animation-timeline: view();`. Cela crée une timeline anonyme et unique pour chaque carte.
- Pour chaque carte individuelle, son animation sera à 0% lorsqu'elle commence tout juste à entrer dans le viewport et atteindra 100% lorsqu'elle vient de finir de quitter le viewport.
Lorsque vous faites défiler la page vers le bas, chaque carte s'animera en douceur pour se mettre en place précisément au moment où elle apparaît. Ceci est réalisé avec seulement deux lignes de CSS, un exploit qui nécessitait auparavant un Intersection Observer en JavaScript et une gestion d'état minutieuse.
Le sujet principal : La synchronisation d'animation
L'utilisation de timelines anonymes `scroll()` et `view()` est puissante pour des effets isolés. Mais que se passe-t-il si nous voulons que plusieurs éléments réagissent à la même timeline ? Imaginez un effet de parallaxe où une image de fond, un titre et un élément de premier plan se déplacent tous à des vitesses différentes mais sont tous pilotés par la même action de défilement. Ou une image de produit qui se transforme à mesure que vous faites défiler une liste de ses fonctionnalités.
C'est là que la synchronisation entre en jeu, et la clé est de passer des timelines anonymes aux timelines nommées.
Pourquoi synchroniser ?
La synchronisation permet la création d'expériences riches et narratives. Au lieu d'une collection d'animations indépendantes, vous pouvez construire une scène cohérente qui évolue à mesure que l'utilisateur fait défiler. C'est essentiel pour :
- Effets de parallaxe complexes : Créer un sentiment de profondeur en déplaçant différentes couches à des vitesses variables par rapport à un seul déclencheur de défilement.
- États de composants coordonnés : Animer différentes parties d'un composant d'interface utilisateur complexe à l'unisson lorsqu'il défile dans le champ de vision.
- Narration visuelle : Révéler et transformer des éléments dans une séquence soigneusement chorégraphiée pour guider l'utilisateur à travers un récit.
Technique : Les timelines nommées partagées
Le mécanisme de synchronisation fait intervenir trois nouvelles propriétés CSS :
- `timeline-scope` : Appliquée à un élément conteneur. Elle établit une portée dans laquelle les timelines nommées définies en son sein peuvent être trouvées par d'autres éléments.
- `scroll-timeline-name` / `view-timeline-name` : Appliquée à un élément pour créer et nommer une timeline. Le nom doit être un identifiant avec des tirets (par exemple, `--ma-timeline`). La progression du défilement (`scroll-timeline-name`) ou la visibilité (`view-timeline-name`) de cet élément devient la source de la timeline nommée.
- `animation-timeline` : Nous l'avons déjà vue, mais maintenant, au lieu d'utiliser `scroll()` ou `view()`, nous lui passons l'identifiant avec des tirets de notre timeline partagée (par exemple, `animation-timeline: --ma-timeline;`).
Le processus est le suivant : 1. Un élément ancêtre définit un `timeline-scope`. 2. Un élément descendant définit et nomme une timeline en utilisant `view-timeline-name` ou `scroll-timeline-name`. 3. Tout autre élément descendant peut alors utiliser ce nom dans sa propriété `animation-timeline` pour se connecter à la même timeline.
Exemple pratique : Une scène de parallaxe multicouche
Construisons un en-tête de parallaxe classique où une image de fond défile plus lentement que la page, et un titre s'estompe plus rapidement.
Structure HTML :
<div class="parallax-container">
<div class="parallax-background"></div>
<h1 class="parallax-title">Mouvement synchronisé</h1>
</div>
<div class="content">
<p>... contenu principal de la page ...</p>
</div>
Implémentation CSS :
/* 1. Définir une portée pour notre timeline nommée */
.parallax-container {
timeline-scope: --parallax-scene;
position: relative;
height: 100vh;
display: grid;
place-items: center;
}
/* 2. Définir la timeline elle-même en utilisant la visibilité du conteneur */
/* Le parcours du conteneur à travers le viewport pilotera les animations */
.parallax-container {
view-timeline-name: --parallax-scene;
}
/* 3. Définir les keyframes pour chaque couche */
@keyframes move-background {
to {
transform: translateY(30vh); /* Se déplace plus lentement */
}
}
@keyframes fade-title {
to {
opacity: 0;
transform: scale(0.8);
}
}
/* 4. Styler les couches et les lier à la timeline nommée */
.parallax-background {
position: absolute;
inset: -30vh 0 0 0; /* Hauteur supplémentaire pour permettre le mouvement */
background: url('https://picsum.photos/1600/1200') no-repeat center center/cover;
z-index: -1;
/* Attacher à la timeline partagée */
animation: move-background linear;
animation-timeline: --parallax-scene;
}
.parallax-title {
color: white;
font-size: 5rem;
text-shadow: 0 0 10px rgba(0,0,0,0.7);
/* Attacher à la même timeline partagée */
animation: fade-title linear;
animation-timeline: --parallax-scene;
}
Explication :
- Le `.parallax-container` établit un `timeline-scope` nommé `--parallax-scene`. Cela rend le nom disponible pour ses enfants.
- Nous ajoutons ensuite `view-timeline-name: --parallax-scene;` au même élément. Cela signifie que la timeline nommée `--parallax-scene` sera une timeline `view()` basée sur la visibilité de `.parallax-container` lui-même.
- Nous créons deux animations différentes : `move-background` pour un décalage vertical subtil et `fade-title` pour un effet de fondu et de mise à l'échelle.
- De manière cruciale, `.parallax-background` et `.parallax-title` ont tous deux leur propriété `animation-timeline` définie sur `--parallax-scene`.
Maintenant, alors que le `.parallax-container` défile à travers le viewport, il génère une seule valeur de progression. Le fond et le titre utilisent tous deux cette même valeur pour piloter leurs animations respectives. Même si leurs keyframes sont complètement différents, leur lecture est parfaitement synchronisée, créant un effet visuel cohérent et impressionnant.
Synchronisation avancée avec `animation-range`
Les timelines nommées sont fantastiques pour faire jouer des animations à l'unisson. Mais que faire si vous voulez qu'elles se jouent en séquence ou qu'une animation se déclenche uniquement pendant une partie spécifique de la visibilité d'un autre élément ? C'est là que la famille de propriétés `animation-range` offre une autre couche de contrôle puissant.
Au-delà de 0% à 100%
Par défaut, une animation est mappée sur toute la durée de sa timeline. `animation-range` vous permet de définir les points de départ et de fin spécifiques de la timeline qui doivent correspondre aux points 0% et 100% des keyframes de votre animation.
Cela vous permet de dire des choses comme : "Commence cette animation lorsque l'élément entre de 20% dans l'écran, et termine-la au moment où il atteint la marque des 50%."
Comprendre les valeurs de `animation-range`
La syntaxe est `animation-range-start` et `animation-range-end`, ou le raccourci `animation-range`.
animation-range: <start-range> <end-range>;
Les valeurs peuvent être une combinaison de mots-clés spéciaux et de pourcentages. Pour une timeline `view()`, les mots-clés les plus courants sont :
entry: Le moment où la boîte de bordure de l'élément franchit le bord de fin du scrollport.exit: Le moment où la boîte de bordure de l'élément franchit le bord de début du scrollport.cover: Couvre toute la période pendant laquelle l'élément recouvre le scrollport, du moment où il le recouvre entièrement au moment où il cesse de le faire.contain: Couvre la période où l'élément est entièrement contenu dans le scrollport.
Vous pouvez également ajouter des décalages en pourcentage à ceux-ci, comme `entry 0%` (le début par défaut), `entry 100%` (lorsque le bord inférieur de l'élément rencontre le bord inférieur du viewport), `exit 0%`, et `exit 100%`.
Exemple pratique : Une scène de narration séquentielle
Créons une liste de fonctionnalités où chaque élément se met en surbrillance à mesure que vous le dépassez en défilant, en utilisant une seule timeline partagée pour une coordination parfaite.
Structure HTML :
<div class="feature-list-container">
<div class="feature-list-timeline-marker"></div>
<div class="feature-item">
<h3>Caractéristique Un : Portée mondiale</h3>
<p>Nos services sont disponibles dans le monde entier.</p>
</div>
<div class="feature-item">
<h3>Caractéristique Deux : Vitesse imbattable</h3>
<p>Faites l'expérience de performances de nouvelle génération.</p>
</div>
<div class="feature-item">
<h3>Caractéristique Trois : Sécurité à toute épreuve</h3>
<p>Vos données sont toujours protégées.</p>
</div>
</div>
Implémentation CSS :
/* Définir la portée sur le conteneur principal */
.feature-list-container {
timeline-scope: --feature-list;
position: relative;
padding: 50vh 0; /* Donner de l'espace pour le défilement */
}
/* Utiliser une div vide dédiée pour définir la source de la timeline */
.feature-list-timeline-marker {
view-timeline-name: --feature-list;
position: absolute;
inset: 0;
}
/* Keyframes pour mettre un élément en surbrillance */
@keyframes highlight-feature {
to {
background-color: lightgoldenrodyellow;
transform: scale(1.02);
}
}
.feature-item {
width: 80%;
margin: 5rem auto;
padding: 2rem;
border: 1px solid #ccc;
border-radius: 8px;
transition: background-color 0.3s, transform 0.3s;
/* Attacher l'animation et la timeline partagée */
animation: highlight-feature linear both;
animation-timeline: --feature-list;
}
/* La magie de animation-range pour le séquençage */
.feature-item:nth-of-type(1) {
animation-range: entry 5% entry 40%;
}
.feature-item:nth-of-type(2) {
animation-range: entry 35% entry 70%;
}
.feature-item:nth-of-type(3) {
animation-range: entry 65% entry 100%;
}
Explication :
- Nous établissons une portée `--feature-list` et créons une timeline `view()` nommée liée à une div de marquage vide qui s'étend sur tout le conteneur. Cette unique timeline suit la visibilité de toute la section des fonctionnalités.
- Chaque `.feature-item` est lié à cette même timeline `--feature-list` et reçoit la même animation `highlight-feature`.
- La partie cruciale est `animation-range`. Sans cela, les trois éléments se mettraient en surbrillance simultanément à mesure que le conteneur défile dans le champ de vision.
- Au lieu de cela, nous assignons des plages différentes :
- Le premier élément s'anime entre 5% et 40% de la progression de la timeline.
- Le deuxième élément s'anime pendant la fenêtre de 35% à 70%.
- Le troisième s'anime de 65% à 100%.
Cela crée un effet séquentiel agréable. En faisant défiler, la première fonctionnalité se met en surbrillance. En continuant de défiler, elle s'estompe tandis que la deuxième se met en surbrillance, et ainsi de suite. Les plages qui se chevauchent (`entry 40%` et `entry 35%`) créent un passage de relais en douceur. Ce séquençage et cette synchronisation avancés sont réalisés avec seulement quelques lignes de CSS déclaratif.
Performance et meilleures pratiques
Bien que les animations CSS pilotées par le défilement soient incroyablement puissantes, il est important de les utiliser de manière responsable. Voici quelques bonnes pratiques clés pour un public mondial.
L'avantage de la performance
Le principal avantage de cette technologie est la performance. Contrairement aux écouteurs de défilement basés sur JavaScript qui s'exécutent sur le thread principal et peuvent être bloqués par d'autres tâches, les animations CSS pilotées par le défilement s'exécutent sur le thread de composition. Cela signifie qu'elles restent parfaitement fluides même lorsque le thread principal est occupé. Pour maximiser cet avantage, limitez-vous à l'animation de propriétés peu coûteuses à composer, principalement `transform` et `opacity`.
Considérations d'accessibilité
Tout le monde ne veut pas ou ne peut pas tolérer le mouvement sur les pages web. Il est crucial de respecter les préférences des utilisateurs. Utilisez la media query `prefers-reduced-motion` pour désactiver ou réduire vos animations pour les utilisateurs qui ont activé ce paramètre dans leur système d'exploitation.
@media (prefers-reduced-motion: reduce) {
.card,
.parallax-background,
.parallax-title,
.feature-item {
/* Désactiver les animations */
animation: none;
/* S'assurer que les éléments sont dans leur état final et visible */
opacity: 1;
transform: none;
}
}
Support des navigateurs et solutions de repli
Fin 2023, les animations CSS pilotées par le défilement sont prises en charge par les navigateurs basés sur Chromium (Chrome, Edge) et sont en développement actif dans Firefox et Safari. Pour un public mondial, vous devez tenir compte des navigateurs qui ne prennent pas encore en charge cette fonctionnalité. Utilisez la règle `@supports` pour n'appliquer les animations que là où elles sont prises en charge.
/* État par défaut pour les navigateurs non compatibles */
.card {
opacity: 1;
transform: translateY(0);
}
/* Appliquer les animations uniquement dans les navigateurs compatibles */
@supports (animation-timeline: view()) {
.card {
opacity: 0; /* État initial pour l'animation */
transform: translateY(50px);
animation: fade-in-up linear;
animation-timeline: view();
}
}
Cette approche d'amélioration progressive garantit une expérience fonctionnelle pour tous les utilisateurs, avec une expérience améliorée et animée pour ceux qui utilisent des navigateurs modernes.
Conseils de débogage
Les outils de développement des navigateurs modernes ajoutent la prise en charge du débogage des animations pilotées par le défilement. Dans les DevTools de Chrome, par exemple, vous pouvez inspecter un élément et trouver une nouvelle section dans le panneau "Animations" qui vous permet de voir la progression de la timeline et de la parcourir manuellement, ce qui facilite grandement l'ajustement de vos valeurs `animation-range`.
Conclusion : L'avenir est piloté par le défilement
Les animations CSS pilotées par le défilement, et en particulier la capacité de les synchroniser avec des timelines nommées, représentent un bond en avant monumental pour la conception et le développement web. Nous sommes passés de solutions JavaScript impératives et souvent fragiles à une approche déclarative, performante et accessible native au CSS.
Nous avons exploré les concepts fondamentaux des timelines `scroll()` et `view()`, qui gèrent respectivement la progression au niveau de la page et de l'élément. Plus important encore, nous avons libéré la puissance de la synchronisation en créant des timelines nommées et partagées avec `timeline-scope` et `view-timeline-name`. Cela nous permet de construire des récits visuels complexes et coordonnés comme des scènes de parallaxe. Enfin, avec `animation-range`, nous avons acquis un contrôle granulaire pour séquencer les animations et créer des interactions complexes et superposées.
En maîtrisant ces techniques, vous ne construisez plus seulement des pages web ; vous créez des histoires numériques dynamiques, engageantes et performantes. À mesure que le support des navigateurs continuera de s'étendre, ces outils deviendront une partie essentielle de la boîte à outils de chaque développeur front-end. L'avenir de l'interaction web est là, et il est piloté par la barre de défilement.