Animations CSS pilotées par le scroll : scroll() et view(), parallax pur CSS, reveal effects et progress bars sans JavaScript ni librairies tierces.
Pourquoi piloter les animations par le scroll ?
Les effets liés au scroll — parallax, barres de progression de lecture, apparitions progressives, sticky headers qui se transforment — étaient l'apanage de bibliothèques JavaScript comme GSAP ScrollTrigger, AOS ou Lenis. Coût : 30 à 80 KB minifiés, listeners scroll coûteux, et synchronisation imparfaite avec le rendu (jank visible sur mobile).
Depuis Chrome 115 (2023), CSS dispose de deux fonctions natives — scroll() et view() — qui permettent d'attacher n'importe quelle animation CSS à la progression du scroll. Tout est géré par le moteur de rendu, sur le thread du compositeur, à 60 FPS minimum, sans une seule ligne de JavaScript.
Comparaison de coût
| Solution | Poids | Performance | JS requis |
|---|---|---|---|
| GSAP ScrollTrigger | ~70 KB gzip | Bonne | Oui |
| AOS (Animate On Scroll) | ~14 KB gzip | Moyenne | Oui |
| IntersectionObserver custom | ~2 KB gzip | Bonne | Oui |
scroll() / view() |
0 KB | Excellente (compositeur) | Non |
scroll() — timeline globale du conteneur
scroll() retourne une timeline dont la progression (0% à 100%) est synchronisée avec la position du scroll d'un conteneur ancêtre. Au début du scroll : 0%. À la fin : 100%.
Syntaxe complète
animation-timeline: scroll(<scroller> <axis>);
/* <scroller> : root | nearest | self | none */
/* <axis> : block | inline | x | y */
/* Exemples */
animation-timeline: scroll(); /* root + block (par défaut) */
animation-timeline: scroll(root block); /* explicite */
animation-timeline: scroll(nearest); /* premier ancêtre scrollable */
animation-timeline: scroll(self); /* l'élément lui-même est scrollable */
Exemple minimal — barre qui se remplit au scroll
<div class="scroll-progress"></div>
@keyframes fillBar {
from { transform: scaleX(0); }
to { transform: scaleX(1); }
}
.scroll-progress {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 4px;
background: #0d6efd;
transform-origin: left;
transform: scaleX(0);
/* Anime fillBar liée au scroll de la racine */
animation: fillBar linear;
animation-timeline: scroll(root);
}
Plus de listener scroll, plus de calcul JS de pourcentage, plus de requestAnimationFrame. Le compositeur du navigateur gère tout en arrière-plan.
view() — timeline de visibilité d'élément
view() diffère de scroll() : la timeline n'est pas liée au scroll global, mais à la visibilité d'un élément précis dans son viewport. 0% = l'élément vient d'apparaître en bas de l'écran. 100% = il vient de disparaître par le haut.
Syntaxe
animation-timeline: view(<axis> <view-timeline-inset>);
/* Exemples */
animation-timeline: view();
animation-timeline: view(block);
animation-timeline: view(block 100px 200px); /* offsets de début/fin */
Reveal effect classique
<section class="reveal-section">
<h2>Titre qui apparaît au scroll</h2>
</section>
@keyframes revealUp {
from {
opacity: 0;
transform: translateY(60px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.reveal-section {
animation: revealUp linear;
animation-timeline: view();
/* L'animation se joue pendant que la section traverse le viewport */
animation-range: entry 0% entry 60%;
/* Joue de 0 à 60% de l'entrée — finie quand l'élément est encore visible */
}
animation-range — contrôle fin du déclenchement
animation-range définit où, dans la timeline view(), l'animation démarre et finit. Cinq mots-clés disponibles :
animation-range: cover 0% cover 100%; /* défaut : toute la traversée */
animation-range: entry 0% entry 100%; /* uniquement l'entrée dans le viewport */
animation-range: exit 0% exit 100%; /* uniquement la sortie du viewport */
animation-range: contain 0% contain 100%; /* l'élément entièrement visible */
animation-range: cover 25% cover 75%; /* milieu de la traversée */
Schéma des plages
entry : 0% lorsque le bord supérieur entre, 100% lorsque le bord supérieur a entièrement traversé.
exit : 0% lorsque le bord inférieur commence à sortir, 100% lorsque tout est sorti.
contain : durée pendant laquelle l'élément est entièrement contenu dans le viewport.
Progress bar de lecture article
Cas d'usage classique des sites blog : une barre fine en haut de page qui se remplit au fur et à mesure que l'utilisateur scrolle.
Implémentation production-ready
<body>
<div class="reading-progress" aria-hidden="true"></div>
<article>
<h1>Titre</h1>
<p>Lorem ipsum…</p>
...
</article>
</body>
@keyframes growProgress {
from { width: 0; }
to { width: 100%; }
}
.reading-progress {
position: fixed;
inset: 0 auto auto 0;
height: 4px;
background: linear-gradient(90deg, #6366f1, #ec4899);
z-index: 9999;
transform-origin: left;
animation: growProgress linear forwards;
animation-timeline: scroll(root block);
}
/* Fallback navigateurs sans support */
@supports not (animation-timeline: scroll()) {
.reading-progress { display: none; }
}
Parallax pur CSS
L'effet parallax — un arrière-plan qui défile plus lentement que le contenu — devient trivial avec scroll().
Hero parallax simple
<section class="hero-parallax">
<img src="background.webp" alt="" class="hero-bg">
<div class="hero-content">
<h1 class="display-3 fw-bold text-white">Bienvenue</h1>
</div>
</section>
@keyframes parallaxBg {
from { transform: translateY(0); }
to { transform: translateY(-30%); }
}
.hero-parallax {
position: relative;
height: 100vh;
overflow: hidden;
}
.hero-bg {
position: absolute;
inset: 0;
width: 100%;
height: 130%; /* hauteur supérieure pour permettre la translation */
object-fit: cover;
animation: parallaxBg linear;
animation-timeline: scroll(root);
}
.hero-content {
position: relative;
z-index: 1;
display: grid;
place-content: center;
height: 100%;
}
Multi-couches parallax
@keyframes parallaxSlow {
from { transform: translateY(0); }
to { transform: translateY(-15%); }
}
@keyframes parallaxFast {
from { transform: translateY(0); }
to { transform: translateY(-50%); }
}
.layer-back { animation: parallaxSlow linear; animation-timeline: scroll(); }
.layer-mid { animation: parallaxSlow linear; animation-timeline: scroll(); animation-duration: 1.5; }
.layer-front { animation: parallaxFast linear; animation-timeline: scroll(); }
Reveal effects au scroll
Remplacer AOS et autres bibliothèques de reveal-on-scroll. Toutes ces animations utilisent view() pour se déclencher exactement quand l'élément entre dans le viewport.
Pattern utilitaires reveal
@keyframes fadeInUp {
from { opacity: 0; transform: translateY(40px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes fadeInLeft {
from { opacity: 0; transform: translateX(-60px); }
to { opacity: 1; transform: translateX(0); }
}
@keyframes scaleIn {
from { opacity: 0; transform: scale(0.85); }
to { opacity: 1; transform: scale(1); }
}
.reveal-up { animation: fadeInUp linear both; animation-timeline: view(); animation-range: entry 0% cover 30%; }
.reveal-left { animation: fadeInLeft linear both; animation-timeline: view(); animation-range: entry 0% cover 30%; }
.reveal-scale { animation: scaleIn linear both; animation-timeline: view(); animation-range: entry 0% cover 30%; }
/* Décalages — pas avec setTimeout, mais avec animation-delay sur une plage */
.reveal-up.delay-1 { animation-range: entry 10% cover 40%; }
.reveal-up.delay-2 { animation-range: entry 20% cover 50%; }
Utilisation dans le HTML
<section class="features">
<article class="reveal-up">
<h3>Performance</h3>
<p>Pas de JS, animations gérées par le compositeur.</p>
</article>
<article class="reveal-up delay-1">
<h3>Accessibilité</h3>
<p>Respect de prefers-reduced-motion natif.</p>
</article>
<article class="reveal-up delay-2">
<h3>Légèreté</h3>
<p>0 KB ajouté à votre bundle JavaScript.</p>
</article>
</section>
Timelines nommées et avancées
Vous pouvez nommer une timeline pour la réutiliser dans plusieurs animations, ou la lier à un conteneur scrollable spécifique qui n'est pas un ancêtre direct.
scroll-timeline-name
.scroll-container {
overflow-y: scroll;
height: 600px;
scroll-timeline-name: --my-scroll; /* déclare la timeline */
scroll-timeline-axis: block;
}
.indicator {
animation: progressIndicator linear;
animation-timeline: --my-scroll; /* utilise la timeline nommée */
}
@keyframes progressIndicator {
from { color: red; }
to { color: green; }
}
view-timeline-name
.target {
view-timeline-name: --target-vt;
}
.related-element {
/* Anime selon la visibilité de .target, pas la sienne */
animation: pulseIn linear;
animation-timeline: --target-vt;
}
Avec @scope (Chrome 118+)
@scope (.gallery) {
:scope img {
animation: fadeIn linear;
animation-timeline: view();
animation-range: entry 0% entry 80%;
}
}
Performance, accessibilité et fallback
Performance — tout sur le compositeur
Les animations scroll() et view() qui ne touchent que transform et opacity tournent intégralement sur le thread du compositeur, sans toucher au layout ni au paint. Résultat : 60 FPS garantis même sur mobile bas de gamme, là où une équivalent JavaScript jankerait.
Accessibilité — prefers-reduced-motion
@media (prefers-reduced-motion: reduce) {
/* Désactiver toutes les animations scroll-driven */
.reveal-up, .reveal-left, .reveal-scale,
.hero-bg, .reading-progress {
animation: none;
}
/* Forcer l'état final pour les reveals */
.reveal-up, .reveal-left, .reveal-scale {
opacity: 1;
transform: none;
}
}
Fallback @supports
/* Style par défaut sans animation */
.reveal-up {
opacity: 1;
transform: none;
}
/* Activation uniquement si le navigateur supporte */
@supports (animation-timeline: scroll()) {
.reveal-up {
opacity: 0;
animation: fadeInUp linear both;
animation-timeline: view();
animation-range: entry 0% cover 30%;
}
}
Détection JavaScript
// Pour ajouter une classe globale si supporté
if (CSS.supports('animation-timeline: scroll()')) {
document.documentElement.classList.add('has-scroll-animations');
}
- Animer uniquement
transformetopacitypour les meilleures perfs - Toujours respecter
prefers-reduced-motion: reduce - Fallback statique pour les ~25% de navigateurs sans support
- Tester sur Chrome ≥ 115 (Android, Desktop) en premier
- Éviter les animations sur des centaines d'éléments simultanés (limite raisonnable : 50)
Conclusion
CSS Scroll-Driven Animations est l'avancée qui rend obsolètes des dizaines de bibliothèques JavaScript. Avec deux fonctions, scroll() et view(), vous obtenez parallax, progress bars, reveal effects et animations contextuelles à coût zéro côté JavaScript et performance native.
En 2026, la couverture navigateurs (~75%) reste perfectible, surtout pour Safari. Mais grâce à @supports, vous pouvez livrer en progressive enhancement dès aujourd'hui : les utilisateurs Chrome/Edge bénéficient des effets, les autres voient le contenu statique sans dégradation. Commencez par les patterns à fort impact (reading progress, hero parallax, reveals) et étendez progressivement.
scroll(): timeline du scroll global du conteneur ancêtreview(): timeline de visibilité d'un élément dans le viewportanimation-range:cover,entry,exit,contain- Timelines nommées :
scroll-timeline-name,view-timeline-name - Tout sur le compositeur — 60 FPS garantis sur mobile
- Toujours respecter
prefers-reduced-motion - Fallback via
@supports (animation-timeline: scroll()) - Remplace GSAP ScrollTrigger, AOS, Lenis pour 80% des cas