Retour au cours

frontend / css

Scroll-driven animations

Leçon 201 exercice

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.

FonctionSource de la timelineExemple d'usage
scroll(root)progression globale du défilement du documentbarre de progression de lecture
view()visibilité de l'élément lui-même dans le viewportapparition 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.

css
/* 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 */
}
css
/* 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) */
}
css
/* 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 */
}
css
/* 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 listener scroll + calcul manuel de pourcentage.
  • view() anime un élément selon sa propre entrée/sortie du viewport, sans IntersectionObserver.
  • animation-range cible une portion précise de la timeline (ex: seulement l'entrée dans le viewport).
  • Une scroll-timeline-name nommée permet à un élément de piloter l'animation d'un autre élément.

Exercices pratiques

1 disponible
1

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.

Résoudre l’exercice →