frontend / css
Scroll-driven animations
Explication
Ce que vous allez apprendre
- Lier une animation à la position de scroll avec
animation-timeline: scroll(), sans JavaScript - Faire apparaître un élément progressivement à son entrée dans le viewport avec
view() - Cibler une portion précise d'une timeline avec
animation-range - Faire piloter l'animation d'un élément par le scroll d'un AUTRE élément (timeline nommée)
Dans quel contexte ?
Un site éditorial veut afficher une barre de progression de lecture en haut de l'écran, et faire apparaître chaque paragraphe en fondu au fur et à mesure du défilement. L'implémentation historique demandait un écouteur scroll recalculant un pourcentage à chaque pixel défilé, avec un risque réel de ralentir la page si le code n'était pas optimisé au requestAnimationFrame près. Cette leçon montre comment obtenir le même résultat en CSS pur.
Étape 1 : un effet courant, réalisé en JavaScript jusqu'ici
Une barre de progression de lecture, ou un élément qui apparaît en fondu à l'écran : ces effets étaient traditionnellement réalisés en écoutant l'événement scroll et en recalculant un pourcentage à chaque déplacement.
Étape 2 : le problème de cette approche
Une opération mal optimisée dans cet écouteur peut ralentir sérieusement le défilement. Les scroll-driven animations déplacent ce calcul entièrement dans le moteur CSS natif, bien plus performant.
Bonne pratique
Une animation pilotée par animation-timeline s'exécute dans le moteur de rendu du navigateur, hors du thread JavaScript principal : elle reste fluide même si votre code JS est occupé ailleurs, contrairement à un calcul fait dans un écouteur scroll.
Étape 3 : changer la "source de temps"
Le concept clé ici est celui de "timeline" : normalement, une animation CSS avance avec le TEMPS. animation-timeline: scroll(...) remplace cette notion de temps par la POSITION DE SCROLL.
Étape 4 : ce que ça permet concrètement
L'animation avance et recule selon que l'utilisateur défile vers le bas ou vers le haut, sans qu'aucun JavaScript n'observe quoi que ce soit.
Étape 5 : scroll() contre view(), deux besoins différents
scroll() lie l'animation à la progression GLOBALE du défilement d'un conteneur. view(), en revanche, lie l'animation à la visibilité PROPRE d'un élément dans son conteneur scrollable — elle démarre à l'entrée et se termine à la sortie.
| Fonction | Source de la timeline | Exemple d'usage |
|---|---|---|
scroll(root) | progression globale du défilement du document | barre de progression de lecture |
view() | visibilité de l'élément lui-même dans le viewport | apparition en fondu au scroll |
Étape 6 : un pattern plus avancé
Il reste un dernier cas à connaître : une timeline de scroll n'est pas forcément liée à l'élément qu'elle anime. On peut nommer une timeline sur un conteneur, puis l'utiliser pour animer un élément complètement différent.
Et ensuite ?
Cette séparation entre "ce qui produit le défilement" et "ce qui réagit" ouvre des possibilités riches. Une fois ces animations maîtrisées, la prochaine étape aborde les transitions natives entre deux vues complètes d'une page.
Commandes & code
Scroll-driven animations
Animer en fonction de la position de scroll, sans écouter le moindre événement scroll en JS.
/* Timeline liée au scroll d'un ancêtre scrollable (souvent le document) */
@keyframes suivre-scroll {
from { transform: scaleX(0); }
to { transform: scaleX(1); }
}
.barre-progression-lecture {
position: fixed;
top: 0;
left: 0;
height: 4px;
width: 100%;
background: var(--couleur-primaire);
transform-origin: left;
animation: suivre-scroll linear;
animation-timeline: scroll(root); /* la timeline = progression du scroll du document */
/* plus besoin de "scroll" + requestAnimationFrame + calcul de pourcentage en JS */
}/* scroll() accepte un axe et une source */
.progression-conteneur {
animation: suivre-scroll linear;
animation-timeline: scroll(nearest block);
/* nearest : ancêtre scrollable le plus proche (pas forcément le document)
block : axe de scroll suivi (block = vertical en écriture LTR standard) */
}/* view() : timeline liée à la visibilité de l'élément dans son conteneur scrollable */
@keyframes apparition-au-scroll {
from {
opacity: 0;
transform: translateY(40px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.carte {
animation: apparition-au-scroll linear both;
animation-timeline: view();
/* la timeline va de "l'élément entre dans le viewport" à "il en sort" */
animation-range: entry 0% cover 40%;
/* ne joue l'animation que pendant l'entrée, jusqu'à 40% de couverture --
reste figé au style final ensuite, pas de rejoue à la sortie */
}/* Named timeline : le scroll d'UN élément pilote l'animation d'un AUTRE */
.galerie {
scroll-timeline-name: --scroll-galerie;
scroll-timeline-axis: inline; /* scroll horizontal */
overflow-x: auto;
display: flex;
}
.indicateur-position {
animation: suivre-scroll linear;
animation-timeline: --scroll-galerie; /* référence la timeline nommée ci-dessus */
}Résumé
animation-timeline: scroll(...)remplace un listenerscroll+ calcul manuel de pourcentage.view()anime un élément selon sa propre entrée/sortie du viewport, sansIntersectionObserver.animation-rangecible une portion précise de la timeline (ex: seulement l'entrée dans le viewport).- Une
scroll-timeline-namenommée permet à un élément de piloter l'animation d'un autre élément.
Exercices pratiques
Mission : remplacer un écouteur scroll qui fait ramer un site éditorial
Objectif : Choisir entre scroll() et view() selon le comportement recherché, et régler une timeline sur une portion précise.
Contexte
Sur article.js, une barre de progression de lecture est actuellement mise à jour par un écouteur scroll qui recalcule un pourcentage à chaque déplacement, provoquant des ralentissements notables sur les téléphones bas de gamme. Par ailleurs, chaque paragraphe .paragraphe de l'article doit apparaître en fondu uniquement lors de son ENTRÉE dans l'écran, sans jamais rejouer l'animation quand l'utilisateur remonte puis redescend au même endroit.