Intégration web angularforall.com

- CSS Scroll-Driven Animations : animer au scroll

Scroll-Animations Animation-Timeline Scroll-Css View-Css Parallax-Css Reveal-Effects Css-Moderne Performance-Web Integration-Web Front-End Core-Web-Vitals No-Javascript Prefers-Reduced-Motion Progressive-Enhancement
CSS Scroll-Driven Animations : animer au scroll

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

cover : 0% lorsque le bord supérieur entre, 100% lorsque le bord inférieur sort.
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; }
}
Plus la page est longue, plus la barre se remplit lentement — le rapport reste juste sans calcul. Aucune mémoire, aucun listener, performance native.

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');
}
Bonnes pratiques :
  • Animer uniquement transform et opacity pour 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.

Récapitulatif :
  • scroll() : timeline du scroll global du conteneur ancêtre
  • view() : timeline de visibilité d'un élément dans le viewport
  • animation-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

Partager