Scroll✨ Premium

Scroll Choreography Sequence

Sept éléments de texte qui entrent un à un — fondu, remontée de 22 px, zoom de 96 à 100 % — chacun dans sa fenêtre data-from / data-to d'une course de 2,2 hauteurs de scène, réversible à la molette. Ils n'en ressortent jamais ; sous prefers-reduced-motion, la scène s'affiche assemblée d'emblée, sans course. Zéro dépendance.

ScrollTimelineStagger

Vous aurez accès à la démo interactive avec un compte gratuit.

Cet effet fait partie d'Effect.Labs — 811 effets vanilla, une partie gratuite, une partie premium. Scroll comprend 70 effets, dont 12 gratuits. Découvrir la catégorie →

3 exemples d'usage

Manifeste de marque en séquence de valeurs — page « Qui sommes-nous » d'un atelier — Scroll Choreography Sequence exemple 1

① Manifeste de marque en séquence de valeurs — page « Qui sommes-nous » d'un atelier

QuandPage « Qui sommes-nous » d'une entreprise : quatre valeurs fondatrices, chacune avec une ligne de preuve, sous un kicker et au-dessus d'une signature.
PourquoiChaque valeur a sa fenêtre dans le HTML — Authenticité de 0 à 20 %, Innovation de 20 à 40 %, Durabilité de 40 à 60 %, Engagement de 60 à 80 % — et le lecteur les découvre au rythme de sa molette, dans l'ordre voulu, sans qu'un chronomètre décide pour lui. Text Highlight Scroll (fx-0591) allume des mots dans un paragraphe continu ; ici ce sont des blocs distincts qui entrent tour à tour. Dans le code vendu ils s'ajoutent les uns sous les autres : à 80 % le manifeste est complet à l'écran — c'est ce qu'on veut pour une liste de valeurs ; pour que chacune occupe la scène seule, voir la FAQ.
RéglagesScène de 540 × 322 px sous une barre de navigation de 38 px (aspect-ratio: auto, hauteur imposée), serif Georgia, accent #f2b64a sur le kicker et la barre, fond radial #2c1f15 → #100c0a ; six .scq-el : kicker 0 → 0,1, quatre valeurs 0 → 0,2 / 0,2 → 0,4 / 0,4 → 0,6 / 0,6 → 0,8, signature 0,82 → 1 ; tabindex="0" et aria-labelledby (vers le kicker) sur le conteneur.
Générique d'introduction d'une conférence projetée — l'animateur fait défiler l'écran — Scroll Choreography Sequence exemple 2

② Générique d'introduction d'une conférence projetée — l'animateur fait défiler l'écran

QuandÉcran de projection d'une keynote : nom de l'événement, titre de la session, deux intervenants avec leur fonction, thème, salle et horaire, que l'animateur fait apparaître à la molette pendant que la salle s'installe.
PourquoiLes fenêtres sont des attributs HTML : l'organisateur qui remplace un intervenant ou décale le thème modifie data-from / data-to dans le balisage, jamais le script. Scroll Snap Sections (fx-0584) rangerait chaque information dans une section pleine page ; ici tout se compose sur une scène épinglée, et le geste inverse remet le titre seul à l'écran — utile pour reprendre le générique quand un retardataire entre.
RéglagesScène pleine section 540 × 360 px, grille de 36 px à 7 % de #7aa2ff en fond, kicker et pied en monospace, titre 30 px ; six .scq-el : kicker 0 → 0,12, titre 0,08 → 0,3, intervenants 0,32 → 0,48 et 0,42 → 0,58 (chevauchés pour la cascade), thème 0,62 → 0,78, salle et horaire 0,84 → 1 ; avatars en initiales, mention « HDMI 1 · 1920 × 1080 » décorative.
Présentation d'un protocole de recherche clinique — les cinq phases d'un essai — Scroll Choreography Sequence exemple 3

③ Présentation d'un protocole de recherche clinique — les cinq phases d'un essai

QuandPage de publication d'un essai clinique : en-tête de revue avec DOI, kicker (essai, phase, effectif), titre, puis Hypothèse → Méthode → Résultats → Discussion → Conclusion en liste numérotée le long d'une ligne de temps.
PourquoiL'ordre du protocole est immuable et le lecteur doit le parcourir dans ce sens : cinq fenêtres consécutives (0,2 → 0,34 … 0,84 → 0,98) l'y obligent, chaque phase entrant sous la précédente comme une ligne de temps qui s'écrit. L'équipe scientifique rallonge une phase en changeant deux nombres dans le HTML. Sticky Reveal Stack (fx-0568) empile des sections plein écran et ne chorégraphie rien à l'intérieur d'une scène commune ; ici la liste se construit sous les yeux, sur fond clair.
RéglagesVariante claire : scène #f6f8fb, texte #101827, accent #0f766e sur pastilles, kicker et barre, piste #d5dde8, repère sur fond sombre à 78 % ; text-align: left sur la scène, liste avec ligne verticale de 2 px et numéros en ::before (content: attr(data-n)) ; sept .scq-el : kicker 0 → 0,1, titre 0,04 → 0,18, cinq phases de 0,14 de large espacées de 0,02.

Comment ça marche

Anatomie : .scq-scene (width: 100%, aspect-ratio: 16/10, overflow: hidden, dégradé radial #20264f → #0a0e1f) contient un conteneur .scq-scroll en position: absolute; inset: 0; overflow-y: auto, barre masquée (scrollbar-width: none doublé de ::-webkit-scrollbar { width: 0 }). Dedans, un espaceur .scq-spacer de 320 % de la hauteur et un bloc .scq-sticky (position: sticky; top: 0, height: 31.25% — exactement 100 / 3,2, soit la hauteur visible du conteneur) qui centre la scène .scq-stage (480 px max, texte centré). Mesuré sur une page vierge de 900 px : scène de 884 × 552,5 px, contenu défilant de 1 768 px, course utile de 1 215 px (2,2 hauteurs) ; à 1 264 px de large, 790 px de haut et 1 738 px de course. Les sept .scq-el — kicker, titre, sous-titre, trois puces, bouton — portent chacun data-from et data-to : 0 → 0,2 ; 0,1 → 0,35 ; 0,25 → 0,5 ; 0,4 → 0,6 ; 0,5 → 0,7 ; 0,6 → 0,8 ; 0,8 → 1.

Le script (ES5, 1 804 caractères commentaire compris, une seule IIFE) parcourt chaque .scq-scene et pose un écouteur scroll passif sur le conteneur plus un resize sur window. À chaque événement, update() calcule la progression p = scrollTop / (scrollHeight − clientHeight), puis pour chaque élément t = clamp((p − from) / (to − from)) entre 0 et 1 — from et to lus par parseFloat(el.dataset.from) || 0 et || 1 — et l'écrit en variable CSS : el.style.setProperty('--t', t.toFixed(3)). Le CSS fait le reste, sans transition ni @keyframes : opacity: var(--t, 0) et transform: translateY(calc((1 − var(--t)) × 22px)) scale(calc(.96 + var(--t) × .04)), avec will-change: opacity, transform. Vérifié à p = 0,375, milieu de la fenêtre du sous-titre : --t: 0.501, opacité 0,501, translateY(10,98px), scale(.98). La barre .scq-prog i lit --scq-p (largeur calc(var(--scq-p) × 100%)) et le repère .scq-hint passe à opacity: 0 (transition .3 s) dès que p > 0.04 — 49 px de molette à cette taille.

C'est un scrubbing : rien n'est chronométré, la position fait tout, et le geste inverse démonte la scène (mesuré : retour en haut, les sept --t reviennent à 0). Les fenêtres se chevauchent — le titre commence à 0,1 quand le kicker est à moitié — d'où la cascade. Mais t ne redescend jamais une fois to dépassé : les éléments entrent et restent. À p = 1, les sept sont à --t: 1.000, réunis dans la même scène ; ce code ne fait pas sortir un élément pour laisser la place au suivant (la FAQ dit comment l'ajouter). Cas limite mesuré : data-from égal à data-to donne une division par zéro, ±Infinity que le clamp ramène à 0 puis 1 — une apparition instantanée à p = from. Ce qui est vendu se limite à cet effet : une IIFE de 1 804 caractères, sans variable globale, zéro bibliothèque, zéro image, zéro police à charger — le CSS déclare lui-même le fond (couleur pleine #0a0e1f sous le dégradé), la police (sans-serif) et le box-sizing de ses descendants, de sorte que la scène se comporte de la même façon collée dans une page vierge que sur cette page (mesuré : 0 requête, 0 erreur console, sept éléments à --t: 1.000 en bout de course). Les écouteurs scroll et resize restent en place tant que la page vit : si vous retirez une scène du DOM dynamiquement, retirez-les aussi, sinon le resize posé sur window garde la scène en mémoire. C'est l'architecture de Sticky Horizontal Reveal (fx-0796 : même conteneur défilant, même bloc épinglé, même barre, même repère au seuil 0,04) appliquée à des fenêtres par élément au lieu d'un déplacement horizontal.

Accessibilité

  • prefers-reduced-motion : la scène s'affiche assemblée, sans course. Le script lit matchMedia('(prefers-reduced-motion: reduce)') et, si la préférence est active, pose la classe scq-reduced sur la scène avant de s'arrêter — c'est cette classe que le CSS attend : espaceur et bloc épinglé à 100% (plus rien à faire défiler), sept éléments forcés à opacity: 1 et transform: none par !important, repère et barre en display: none. Mesuré avec la préférence active sur une page vierge de 1 280 px : course de 0 px, sept éléments à opacité 1 dès le chargement, contenu centré dans la scène, repère et barre absents. Le choix est délibéré : plutôt qu'un @media parallèle, une seule classe pilote l'état sans mouvement, et vous pouvez la poser à la main (voir personnalisation).
  • Clavier et repère : le conteneur overflow-y: auto n'a pas de tabindex et sa barre est masquée sur tous les navigateurs. Mesuré dans Chromium : Tab atteint bien le conteneur (zones défilantes focalisables depuis Chrome 130) et deux flèches bas le font avancer de 80 px — mais ni Safari ni les Chrome antérieurs ne le font. Ajoutez tabindex="0" et un aria-label au .scq-scroll, et gardez une indication visible : le repère s'efface après 4 % de course, et la barre de 3 px (piste à 1,25:1 sur le fond, remplissage #5cffd8 à 15,2:1) est ensuite le seul rappel qu'il reste à défiler.
  • Lecteurs d'écran : les sept éléments restent dans l'arbre d'accessibilité à toute position (opacité seule, ni visibility ni display), donc le texte complet est lu dans l'ordre du HTML quelle que soit la scène affichée — c'est le bon comportement, ne posez pas aria-hidden dessus. Le repère et la barre portent déjà aria-hidden="true". Le bouton « Commencer » est un span : remplacez-le par un a ou un button, et sachez qu'un élément à opacité 0 reçoit les clics (aucun pointer-events: none ; mesuré à p = 0, elementFromPoint au centre du bouton renvoie le bouton) — il est cliquable avant d'être visible.
  • Contraste et cadrage : mesuré dans Chromium sur les pixels sous le texte, kicker #5cffd8 12,6:1, titre 15,9:1, sous-titre #c7cbe0 10,1:1, puces blanches sur 10 % de blanc 12,0:1, bouton #0a0e1f sur blanc 16,8:1, repère de 12 px 17,2:1 — tout passe. Le problème est ailleurs : la hauteur vient du seul aspect-ratio: 16/10. Mesuré à 360 px de large (scène de 344 × 215 px), le titre passe sur deux lignes et le contenu assemblé mesure 217 px : le bas du bouton dépasse de 22 px sous la barre de progression, coupé par overflow: hidden (à 320 px : 217 px de contenu pour 200 de scène) ; à 364 px de scène, tout tient (187 px pour 227). Le repère, lui, se plie sur deux lignes dès que la scène fait moins de 396 px (left: 50% ne lui laisse que la moitié de la largeur ; mesuré : deux lignes à 384 px, une à 404 px). Donnez une min-height à la scène (ou un ratio plus haut sous 480 px) et white-space: nowrap au repère.

Compatibilité navigateur

JavaScript ES5 (var, function) avec dataset, NodeList.forEach, classList, matchMedia et style.setProperty sur une propriété personnalisée ; CSS : position: sticky, inset, variables dans calc() à l'intérieur de transform, will-change, isolation, et surtout aspect-ratio, seule source de la hauteur de la scène — sans lui, mesuré, la scène fait 0 px de haut. C'est lui qui fixe le plancher : Chrome 88, Firefox 89, Safari 15. Aucun animation-timeline : contrairement aux effets de défilement en CSS natif de cette catégorie (Stagger Cards fx-0540, Stagger Reveal CSS fx-0552, fx-0545, fx-0546…), que Firefox ne lit dans aucune version, cette chorégraphie fonctionne sur Firefox. scrollbar-width: none (Chrome 121, Firefox 64, Safari 18.2) est doublé par ::-webkit-scrollbar { width: 0 } : la barre est masquée partout. Zéro dépendance : aucune bibliothèque, aucune image, aucune police à charger — le fichier vendu ne contient que cet effet.

Chrome 88+✓ Complet
Firefox 89+✓ Complet
Safari 15+✓ Complet
Edge 88+✓ Complet
Mobile iOS 15+✓ Complet (défilement tactile de la zone ; contenu coupé sous 355 px de largeur de scène)
Android Chrome 88+✓ Complet (défilement tactile de la zone ; contenu coupé sous 355 px de largeur de scène)

Sans JavaScript, la scène est vide : les sept éléments restent à opacity: 0 (valeur par défaut de var(--t, 0)), le repère « Scrollez pour composer la scène » s'affiche et 1 215 px de course défilent pour rien. Sans aspect-ratio (navigateurs antérieurs à 2021), la scène n'a aucune hauteur et n'occupe rien : donnez-lui une height explicite. Sous prefers-reduced-motion, le script pose scq-reduced : scène assemblée d'emblée, sans course (voir accessibilité).

Le code

Structure HTML à coller dans votre page (CSS + JS disponibles avec un compte premium) :

index.html — structure
<div class="scq-scene">
  <div class="scq-hint" aria-hidden="true">Scrollez pour composer la scène</div>
  <div class="scq-prog" aria-hidden="true"><i></i></div>
  <div class="scq-scroll">
    <div class="scq-spacer">
      <div class="scq-sticky">
        <div class="scq-stage">
          <div class="scq-el scq-kicker" data-from="0" data-to="0.2">…</div>
          <div class="scq-el scq-title" data-from="0.1" data-to="0.35">…</div>
          <p class="scq-el scq-sub" data-from="0.25" data-to="0.5">…</p>
          <div class="scq-chips">
            <span class="scq-el scq-chip" data-from="0.4" data-to="0.6">…</span>
            <span class="scq-el scq-chip" data-from="0.5" data-to="0.7">…</span>
            <span class="scq-el scq-chip" data-from="0.6" data-to="0.8">…</span>
          </div>
          <span class="scq-el scq-cta" data-from="0.8" data-to="1">…</span>
        </div>
      </div>
    </div>
  </div>
</div>
🔒 Débloquer le code complet — à partir de 2,99 € le 1er mois

HTML + CSS + JS complet, prêt à coller — avec des centaines d'effets premium.

Personnaliser

Options passées à l'API ou attributs data-* :

Option / propriétéDéfautEffet
data-from / data-to (HTML, par élément) 0 → 0,2 ; 0,1 → 0,35 ; 0,25 → 0,5 ; 0,4 → 0,6 ; 0,5 → 0,7 ; 0,6 → 0,8 ; 0,8 → 1 Fenêtre d'entrée de chaque élément, en fraction de la course : --t vaut 0 avant from, 1 après to, linéaire entre les deux. Chevauchez les fenêtres pour une cascade, espacez-les pour un tour de rôle ; attribut absent = 0 et 1 (l'élément entre sur toute la course) ; from égal à to = apparition instantanée. Un data-to="0" est lu comme 1 (|| 1).
Longueur de course (CSS — .scq-spacer { height: 320% } et .scq-sticky { height: 31.25% }) 320 % / 31,25 % — 2,2 hauteurs de scène de molette (1 215 px à 884 px de large) Les deux valeurs sont couplées : la hauteur du bloc épinglé doit rester 100 / (espaceur ÷ 100), sinon il dépasse ou laisse un vide. 250%40% : course de 1,5 hauteur, entrées plus rapides ; 400%25% : trois hauteurs, plus de temps par élément.
Format de la scène (CSS — .scq-scene { width: 100%; aspect-ratio: 16/10 }) 16/10, pleine largeur — 552 px de haut à 884 px, 790 px à 1 264 px, 215 px à 344 px Seule source de la hauteur. Ajoutez max-width pour ne pas obtenir une scène de 790 px sur grand écran, et min-height: 360px (ou un ratio plus haut sous 480 px) pour que le contenu assemblé tienne sur mobile — mesuré, il déborde de 22 px à 360 px de large, dès que la scène fait 354 px ou moins.
Amplitude d'entrée (CSS — .scq-el : 22px et .96 + … × .04) 22 px de remontée, zoom de 96 à 100 % Trajet de chaque élément entre --t 0 et 1, en plus du fondu. 40px pour une montée franche, 0px pour un fondu pur ; .9 + … × .1 accentue le zoom. Le tout reste sur le compositeur grâce à will-change.
Seuil du repère (JS — p > 0.04) et son texte (HTML — .scq-hint) 4 % de la course (49 px à 884 px de large), « Scrollez pour composer la scène » Le repère s'efface (transition .3 s) dès ce seuil et revient si l'on remonte. Montez-le à 0.15 pour qu'il accompagne la première entrée ; ajoutez white-space: nowrap pour qu'il ne se plie pas sous 396 px de scène.
Couleurs (CSS) fond #0a0e1f sous radial-gradient(circle at 50% 30%, #20264f, #0a0e1f 72%), accent #5cffd8 (kicker, barre), sous-titre #c7cbe0, puces à 10 % / 18 % de blanc, bouton blanc sur #0a0e1f L'accent sert deux fois (kicker et .scq-prog i) : changez-le aux deux endroits. La couleur pleine posée sous le dégradé est le fond effectif de la scène (repli si le dégradé ne s'applique pas, et ce que lisent les outils de contraste) : changez-la avec lui. Sur fond clair, inversez aussi .scq-stage { color }, le fond du repère (rgba(0,0,0,.4)) et la piste (rgba(255,255,255,.14)).
Police (CSS — .scq-scene { font-family: sans-serif }) sans-serif (générique du système) La scène déclare sa police pour ne pas hériter de celle de la page hôte (Times par défaut sur une page vierge). Remplacez-la par celle de votre site, ou mettez inherit pour reprendre celle du contexte — c'est ce que font les trois scènes d'exemple ci-dessus.
Classe scq-reduced (HTML, sur .scq-scene) absente (posée par le script sous prefers-reduced-motion) État sans mouvement : espaceur et bloc épinglé à 100 % (plus de course), sept éléments forcés visibles par !important, repère et barre masqués, script inerte. Le script la pose lui-même quand prefers-reduced-motion est actif ; posez-la à la main pour livrer la scène assemblée sans la préférence système — capture, version imprimable, aperçu dans un e-mail.
Nombre d'éléments (HTML — tout élément portant .scq-el) 7 Le script anime tout .scq-el de la scène, quel que soit son balisage ; le coût par événement scroll est de n écritures de --t. Chaque élément est une couche de composition (will-change) : au-delà d'une trentaine, retirez cette règle.

FAQ

Pas avec ce code : t = clamp((p − from) / (to − from)) ne fait que monter, et une fois to dépassé l'élément reste à --t: 1. Mesuré en bout de course, les sept éléments sont affichés ensemble — la « scène qui s'assemble » du catalogue est exacte, la scène qui se relaie n'existe pas. Pour l'obtenir, quatre lignes dans update() : lisez data-out et data-out-to (parseFloat(el.dataset.out), avec || 2 pour ne jamais sortir par défaut), calculez u = 1 − clamp((p − out) / (outTo − out)), et écrivez Math.min(t, u) à la place de t (vérifié : titre avec data-out="0.9" data-out-to="1", --t = 0,502 à p = 0,95 et 0 à p = 1). Comme --t pilote à la fois l'opacité et la remontée, la sortie rejoue l'entrée à l'envers : l'élément redescend de 22 px en s'effaçant. Donnez alors à chaque valeur du manifeste data-from="0" data-to="0.1" data-out="0.18" data-out-to="0.2", la suivante à partir de 0,2, et sortez-les du flux (position: absolute dans .scq-stage), sinon la place du précédent reste vide.
Mesuré dans une page ordinaire, scène de 562 px de haut : les treize premiers crans de 100 px vont au conteneur (1 237 px), la page ne repart qu'au quatorzième, et rien (overscroll-behavior absent) ne l'annonce. C'est le prix d'un conteneur imbriqué à barre masquée. Pour un vrai bloc de page : supprimez .scq-scroll, donnez à .scq-spacer une hauteur en vh (320vh) et à .scq-sticky height: 100vh ; dans le script, remplacez l'écouteur du conteneur par window.addEventListener('scroll', update) et calculez p = clamp(−rect.top / (spacer.offsetHeight − innerHeight)) avec rect = spacer.getBoundingClientRect(). Tout le reste — fenêtres, --t, barre, repère — est inchangé. Si vous gardez le conteneur, raccourcissez au moins l'espaceur à 250% / 40%.
Deux mécaniques différentes. Stagger Cards est du CSS pur (0 octet de JS) : chaque carte anime opacity et translateY(30px) sur sa propre timeline animation-timeline: view(), entre entry et entry 40% de son arrivée dans la fenêtre, avec un décalage fixe de 50 ms par :nth-child ; la cascade vient de la disposition — chaque carte entre quand elle apparaît — et se règle en CSS, sans attribut par élément. Scroll Choreography partage une horloge unique p pour tous les éléments d'une scène épinglée, et c'est le HTML qui dit qui entre quand : un titre à 10 %, un bouton à 80 %, sans rapport avec leur position dans la page. Deuxième différence, décisive selon votre audience : animation-timeline n'existe dans aucune version de Firefox, où Stagger Cards ne cascade pas ; fx-0797 y fonctionne. Prenez fx-0540 pour une grille de cartes qui se révèle au fil de la page sur Chrome et Safari ; fx-0797 pour une scène composée dans l'ordre que vous écrivez, partout.