Pourquoi lier une animation au scroll ?
Les animations déclenchées par le défilement sont partout : barre de progression de lecture, apparition progressive d’une section, parallaxe légère, indicateur d’étape, galerie qui se révèle au fur et à mesure. Pendant longtemps, la solution courante consistait à écouter l’événement scroll en JavaScript, calculer une progression, puis modifier des styles à chaque frame.
Cette approche fonctionne, mais elle devient vite fragile. Elle peut provoquer des recalculs de layout inutiles, multiplier les accès au DOM, et rendre le code difficile à maintenir. Pour certains cas, Intersection Observer reste une excellente API, notamment lorsqu’il s’agit de détecter si un élément entre dans la zone visible. Mais pour piloter une animation en continu avec la progression du scroll, CSS dispose désormais d’un outil beaucoup plus direct : les scroll-driven animations.
Le principe est simple : au lieu de dire « lance cette animation pendant 600 ms », on dit « fais progresser cette animation selon la position de scroll ». Le temps n’est plus l’unité principale. La progression de défilement devient la timeline.
Le modèle mental : animation classique contre animation pilotée par le scroll
Une animation CSS classique ressemble souvent à ceci :
.card {
animation: fade-in 600ms ease-out both;
}
@keyframes fade-in {
from {
opacity: 0;
transform: translateY(1rem);
}
to {
opacity: 1;
transform: translateY(0);
}
}
Ici, l’animation dépend du temps. Dès qu’elle démarre, elle avance jusqu’à sa fin, que l’utilisateur fasse défiler la page ou non.
Avec une animation liée au scroll, on garde les @keyframes, mais on remplace la timeline temporelle par une timeline de défilement. CSS peut alors synchroniser l’état de l’animation avec la position de l’utilisateur dans la page.
Deux grandes familles existent :
- les animations liées à la progression d’un conteneur de scroll ;
- les animations liées à la visibilité d’un élément dans la zone d’affichage.
La première est pratique pour une barre de progression globale. La seconde est idéale pour animer une section lorsqu’elle entre et sort du viewport.
Créer une barre de progression de lecture
Commençons par un cas simple : une barre fixe en haut de page qui se remplit au fur et à mesure de la lecture.
<div class="reading-progress"></div>
<article class="article">
<h2>Un long article</h2>
<p>...</p>
</article>
.reading-progress {
position: fixed;
top: 0;
left: 0;
z-index: 10;
width: 100%;
height: 4px;
transform-origin: left;
transform: scaleX(0);
background: currentColor;
animation: reading-progress linear both;
animation-timeline: scroll(root block);
}
@keyframes reading-progress {
to {
transform: scaleX(1);
}
}
animation-timeline: scroll(root block) indique que l’animation doit suivre le défilement vertical du document racine. Quand la page est en haut, la barre est à scaleX(0). Quand on atteint la fin du scroll, elle arrive à scaleX(1).
L’intérêt est important : aucun gestionnaire scroll, aucun calcul manuel, aucune synchronisation avec requestAnimationFrame. Le navigateur peut optimiser l’animation avec une connaissance directe du contexte de rendu.
Animer un élément selon sa visibilité
Pour animer une carte quand elle entre dans le viewport, on peut utiliser une timeline de vue avec view().
<section class="feature-card">
<h2>Formation JavaScript avancée</h2>
<p>Apprenez à structurer des interfaces performantes et maintenables.</p>
</section>
.feature-card {
opacity: 0;
transform: translateY(2rem);
animation: reveal-card linear both;
animation-timeline: view();
animation-range: entry 0% cover 35%;
}
@keyframes reveal-card {
to {
opacity: 1;
transform: translateY(0);
}
}
animation-timeline: view() associe l’animation à la visibilité de l’élément. animation-range permet de préciser la plage utile. Dans cet exemple, l’animation commence quand l’élément entre dans la zone visible et se termine lorsqu’il couvre environ 35 % de sa progression dans le viewport.
Cette syntaxe est très expressive, mais elle demande un peu de pratique. Une bonne méthode consiste à commencer avec des transformations simples : opacity, transform, éventuellement filter avec modération. Évitez d’animer des propriétés coûteuses comme width, height, top ou left, qui peuvent déclencher du layout.
Pour approfondir la mesure réelle de ces choix en production, vous pouvez compléter cette approche avec PerformanceObserver, notamment si vos pages combinent animations, images lourdes et contenus dynamiques.
Utiliser une timeline nommée
Dès que l’interface devient plus complexe, il peut être utile de nommer une timeline. Imaginons une zone scrollable indépendante, par exemple un panneau de documentation ou une liste de résultats.
<div class="panel">
<div class="panel-progress"></div>
<div class="panel-content">
<p>Beaucoup de contenu...</p>
</div>
</div>
.panel {
max-height: 24rem;
overflow: auto;
scroll-timeline-name: --panel-scroll;
scroll-timeline-axis: block;
border: 1px solid currentColor;
}
.panel-progress {
position: sticky;
top: 0;
height: 3px;
transform: scaleX(0);
transform-origin: left;
background: currentColor;
animation: panel-progress linear both;
animation-timeline: --panel-scroll;
}
@keyframes panel-progress {
to {
transform: scaleX(1);
}
}
La timeline --panel-scroll est déclarée sur .panel, puis utilisée par .panel-progress. Cette séparation rend le code plus lisible : le conteneur définit la source de progression, les éléments enfants décident comment l’utiliser.
C’est particulièrement utile dans une architecture de composants. Une carte, un panneau ou une section immersive peut embarquer sa propre logique CSS sans dépendre d’un script global. Cette philosophie rejoint celle des container queries CSS : rendre les composants plus autonomes en les faisant réagir à leur contexte direct.
Ajouter un fallback avec progressive enhancement
Les scroll-driven animations ne doivent pas devenir une condition nécessaire pour comprendre ou utiliser la page. Une interface doit rester lisible si la fonctionnalité n’est pas supportée, si l’utilisateur réduit les animations, ou si le contexte matériel est limité.
On peut utiliser @supports pour activer ces animations uniquement lorsque le navigateur les comprend.
.feature-card {
opacity: 1;
transform: none;
}
@supports (animation-timeline: view()) {
.feature-card {
opacity: 0;
transform: translateY(2rem);
animation: reveal-card linear both;
animation-timeline: view();
animation-range: entry 0% cover 35%;
}
}
Ajoutez également une règle pour les personnes qui préfèrent limiter les animations :
@media (prefers-reduced-motion: reduce) {
.reading-progress,
.feature-card,
.panel-progress {
animation: none;
transform: none;
opacity: 1;
}
}
Cette approche est un bon exemple de progressive enhancement : le contenu reste disponible, puis l’expérience s’améliore quand le navigateur et les préférences utilisateur le permettent.
Quand utiliser JavaScript malgré tout ?
CSS est excellent pour exprimer une relation visuelle entre le scroll et une animation. En revanche, JavaScript reste pertinent lorsque la progression déclenche une logique applicative : charger des données, synchroniser une URL, piloter un état React, envoyer une mesure analytique ou coordonner plusieurs systèmes externes.
Par exemple, pour mettre à jour une variable CSS depuis JavaScript, on peut encore utiliser une approche contrôlée :
const root = document.documentElement;
function updateScrollRatio() {
const max = root.scrollHeight - window.innerHeight;
const ratio = max > 0 ? window.scrollY / max : 0;
root.style.setProperty('--scroll-ratio', ratio.toFixed(4));
}
window.addEventListener('scroll', updateScrollRatio, { passive: true });
updateScrollRatio();
Mais ce code doit rester l’exception pour les cas où CSS ne suffit pas. Pour une barre de progression, une révélation visuelle ou une parallaxe simple, les scroll-driven animations sont généralement plus déclaratives et plus faciles à maintenir.
Bonnes pratiques de production
Gardez vos animations sobres. Une page où chaque bloc bouge, change d’échelle ou se décale pendant le scroll fatigue vite l’utilisateur. Le scroll est déjà une interaction ; l’animation doit clarifier la progression, pas voler l’attention.
Privilégiez les propriétés compositées comme transform et opacity. Testez sur mobile, où les contraintes de performance et de lisibilité sont plus fortes. Combinez les animations de scroll avec des images correctement optimisées, surtout sur les pages éditoriales longues ; l’article sur l’optimisation des images web complète bien ce sujet.
Enfin, ne confondez pas les scroll-driven animations avec les transitions entre pages ou entre états d’interface. Pour ces cas, la View Transitions API répond à un autre problème : animer un changement de vue, pas une progression de lecture.
Conclusion
Les scroll-driven animations déplacent une partie importante des animations de scroll depuis JavaScript vers CSS. Elles permettent de créer des barres de progression, révélations de sections et effets contextuels avec une syntaxe déclarative, lisible et mieux alignée avec le moteur du navigateur.
Comme souvent en frontend moderne, la bonne approche n’est pas de tout animer. Il s’agit de choisir les endroits où l’animation aide réellement l’utilisateur à comprendre la structure de la page, puis de l’implémenter avec des fallbacks, le respect de prefers-reduced-motion et une attention constante à la performance.