Scroll✨ Premium

Timeline Scroll

Six jalons le long d'une ligne de 2 px dont la hauteur suit le défilement au pourcentage près — scrollTop ÷ course (419 px) × 100 — et un IntersectionObserver à seuil 0,4 qui révèle chaque carte une fois pour toutes : 500 ms de fondu et 20 px de glissement. À la remontée la ligne recule, les cartes restent ; trois des six sont déjà révélées avant le premier scroll.

JavaScriptCSSTimeline

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

Étapes d'une procédure contentieuse — portail de suivi judiciaire — Timeline Scroll exemple 1

① Étapes d'une procédure contentieuse — portail de suivi judiciaire

QuandPortail d'accès au dossier : l'avocat ou la partie ouvre la chronologie de la procédure (dépôt de plainte, mise en examen, renvoi en jugement, audience et délibéré, notification) et fait défiler ; une colonne à droite affiche l'avancement.
PourquoiLa hauteur de la ligne est une variable continue, directement liée au défilement : elle exprime la part de parcours accomplie plus finement qu'une rangée de points, et le même ratio nourrit le pourcentage et le compte des jalons atteints dans la colonne de droite. Les cartes, elles, donnent le détail de chaque acte — deux informations en une seule interaction.
RéglagesZone de 340 × 300 px (course 242 px), cinq étapes de 84 px ; la ligne part du centre du premier jalon et s'arrête au centre du dernier (top: 69px; bottom: 135px, 338 px) pour que 0 % et 100 % signifient « première » et « dernière étape » ; point recentré sur la ligne (left: -20px), descriptions à .55 (5,3:1). La colonne lit le ratio sur le même événement scroll : pourcentage, barre, et jalons atteints = points dont le centre est au-dessus de la pointe (69 + r × 338 px). Les deux étapes à venir gardent un point creux sous .visible.
Historique de consultations — carnet de santé partagé — Timeline Scroll exemple 2

② Historique de consultations — carnet de santé partagé

QuandInterface web d'un carnet de santé sur la tablette d'un médecin remplaçant : consultations, ordonnance, hospitalisation, examen et vaccin d'une patiente sur un axe daté, découverts en séquence au premier parcours de l'historique.
PourquoiL'aller simple de l'observateur est ici le point décisif : une entrée révélée ne disparaît plus, le médecin peut remonter pour comparer une hospitalisation de mai à une échographie de juin sans que l'une des deux ne s'efface — l'inverse du mode bidirectionnel de fx-0577, choisi à dessein pour la comparaison d'antécédents. Le compteur d'entrées consultées ne redescend jamais, pour la même raison.
RéglagesZone de 368 × 316 px (course 256 px), six entrées de 70 à 85 px typées par un badge (consultation, ordonnance, hospitalisation, examen, vaccin), dégradé #22d3ee → #3b82f6 et point cyan, date en minuscules ; ligne du premier au dernier jalon (top: 69px; bottom: 121px, 382 px). Instance propre (#hs-scroll, #hs-fill, [data-tl2]) : le code vendu ne pilote que #timeline-scroll. Compteur alimenté par un MutationObserver sur la classe visible.
Traçabilité d'un lot de fabrication — outil qualité industriel — Timeline Scroll exemple 3

③ Traçabilité d'un lot de fabrication — outil qualité industriel

QuandSystème de gestion de la qualité : le flux d'un lot (réception matière, mélange, moulage, contrôle dimensionnel, conditionnement, expédition) sur un axe vertical, avec un écart enregistré à la quatrième étape ; le responsable qualité défile pour le situer et mesurer ce qui est déjà validé.
PourquoiLa double mécanique encode deux choses à la fois : la progression globale du lot par la hauteur de la ligne — verte, puis ambre à hauteur de l'écart — et le détail de chaque étape par sa carte, dont le point change de couleur selon le statut. Une barre de progression seule ne dirait pas où est l'écart ; une liste seule ne dirait pas quelle part du flux est franchie.
RéglagesZone de 340 × 300 px (course 296 px), six étapes ; dégradé #22c55e jusqu'à 56 % puis #f59e0b dès 64 % de la ligne, l'écart se trouvant à 62 % (point à 322 px sur une ligne de 407 px, de 69 à 476 px) ; jalon en écart ambre avec halo de 14 px, étapes bloquées à point creux gris #475569, heures en monospace ; colonne « flux parcouru » sur le même ratio.

Comment ça marche

Le bloc #timeline-scroll (.scroll-container, 280px de haut, overflow: hidden auto, scroll-behavior: smooth) contient .timeline-inner (position: relative, marges internes 20px 16px 20px 40px) : une piste .timeline-line absolue de 2 px à left: 24px, tendue de top: 20px à bottom: 20px — donc sur tout le contenu, pas sur la fenêtre — et, dedans, #timeline-fill à height: 0%, dégradé vertical #6366f1 → #a855f7 → #ec4899, transition: height .15s linear. Chaque .timeline-item[data-timeline] naît à opacity: 0 et translateX(20px) avec une transition de .5s cubic-bezier(.16, 1, .3, 1) ; son point de 10 px (#1a1a2e, bordure à 10 % de blanc) passe au bleu #6366f1 avec un halo de 12 px sous .visible. Mesuré dans Chromium : 699 px de contenu — un espaceur de 60 px en tête, six cartes de 79,8 px, deux espaceurs de 60 px en pied — soit une course de 419 px ; ligne de 658,8 px, points centrés à 89, 169, 249, 328, 408 et 488 px, et 3 px à gauche de la ligne (centre du point à 22 px, ligne de 24 à 26 px).

Premier canal, la ligne. Un écouteur scroll écrit scrollTop / (scrollHeight − clientHeight) × 100 dans le style.height du remplissage, en pourcentage de la ligne : 9,55 % à 40 px de course, 100 % à 419 px, 0 % au retour en haut. Comme la ligne couvre tout le contenu, la pointe se trouve, depuis le haut du cadre, à 20 + 240 × r px (r = ratio de défilement) : elle balaie de 20 à 260 px sur les 280 du cadre et reste toujours visible. La transition de 150 ms la fait traîner derrière la molette : après un saut de 210 px, 18 px de remplissage à 13 ms, 165 px à 80 ms, les 330 px visés (50,1 %) à 163 ms. Aucune animation-timeline : c'est un écouteur classique.

Second canal, les cartes. Un IntersectionObserver à root: timelineScroll, threshold: .4 et rootMargin: '0px' ajoute visible dès que 40 % de la hauteur d'une carte (31,9 px) est dans le cadre, et ne la retire jamais — aller simple, là où fx-0577 (Reveal on Scroll) la retire à la sortie. Mesuré : 43 % d'opacité 50 ms après le seuil, 72 % à 100 ms, 93 % à 200 ms, stable à 500 ms. Les deux canaux ne sont pas synchrones : les cartes 4, 5 et 6 sont révélées à 72, 152 et 232 px de course (17, 36 et 55 %) alors que la ligne n'atteint leur point qu'à 196, 247 et 298 px (47, 59 et 71 %) — chaque carte apparaît de 66 à 124 px avant que la ligne ne la rejoigne, et les 45 % de course restants ne remplissent que 191 px de ligne vide sous le dernier jalon. Au chargement, les cartes 1 et 2 portent déjà visible dans le HTML vendu et la 3e (50,6 % dans le cadre) est révélée par l'observateur : rien ne s'anime avant la 4e.

Accessibilité

  • prefers-reduced-motion absent du code : mesuré sous émulation reduce, la transition de 500 ms des cartes (72 % d'opacité à 100 ms, identique), le remplissage de 150 ms et la pulsation de 2 s de l'étiquette sont inchangés. Le mouvement est piloté par le lecteur, mais le glissement de 20 px et le remplissage animé restent des animations : ajoutez @media (prefers-reduced-motion: reduce) { .timeline-item, .timeline-line-fill, .timeline-dot { transition: none } .scroll-hint-label { animation: none } .scroll-container { scroll-behavior: auto } } — les cartes apparaissent d'un coup, la ligne saute, l'information est conservée.
  • Contraste : titre #fff 19,75:1 ; année #a855f7 4,99:1 à 10,4 px (conforme de justesse) ; description rgba(255,255,255,.4) à 11,2 px = 3,77:1, sous les 4,5:1 requis — passez-la à .5 (5,3:1). L'étiquette « Scrollez pour explorer » culmine à 2,62:1 au repos et reste affichée, pulsante, une fois la course finie : retirez-la ou masquez-la à 100 %. Le point éteint (#1a1a2e, 1,16:1) est un état décoratif volontairement invisible. Sur fond clair rien ne tient (description 1,0:1, année 3,96:1) : redéfinissez les quatre couleurs.
  • Clavier et tactile : mesuré dans Chromium, Tab donne le focus à #timeline-scroll et chaque flèche bas défile de 40 px — 9,5 % de ligne, et la révélation des cartes par crans ; Firefox et Safari ne focalisent pas un bloc défilant sans tabindex="0", ajoutez-le avec un :focus-visible. L'observateur ne dépend d'aucun pointeur : molette, doigt, clavier et scrollTo révèlent de la même façon (attention, scroll-behavior: smooth fait glisser tout scrollTop assigné : ~350 ms mesurés pour 419 px).
  • Lecteurs d'écran et sans JavaScript : six div sans sémantique — préférez ol > li avec un aria-label (« Étape 3 sur 6 »). À opacity: 0 les cartes restent dans l'arbre d'accessibilité : tout le contenu est lu avant même d'être révélé, ce qui convient. Sans script (mesuré) : les deux premières cartes s'affichent grâce à leur classe en dur, les quatre autres restent à opacité 0 et la ligne vide — prévoyez <noscript><style>.timeline-item{opacity:1;transform:none}</style></noscript>. Posez aria-hidden="true" sur le point, la ligne et l'étiquette.

Compatibilité navigateur

ES2015 (const, fonctions fléchées, NodeList.forEach) et IntersectionObserver avec l'option root — Chrome 51+, Firefox 55+, Safari 12.1+, Edge 15+. Le CSS repose sur la forme à deux valeurs overflow: hidden auto (Chrome 68+, Firefox 61+, Safari 12.1+) et sur scroll-behavior: smooth (ignoré par Safari avant 15.4, sans conséquence). Pas d'animation-timeline ni de scroll-timeline : ligne et cartes sont pilotées par un écouteur scroll et un observateur d'intersection, l'effet fonctionne donc sur Firefox, qui n'implémente pas les animations CSS pilotées au scroll. Zéro dépendance.

Chrome 68+✓ Complet
Firefox 61+✓ Complet (barre de défilement native)
Safari 12.1+✓ Complet (défilement doux ignoré avant 15.4)
Edge 79+✓ Complet
Mobile iOS 12.2+✓ Complet (défilement tactile du bloc)
Android Chrome✓ Complet

Sans JavaScript (mesuré) : le bloc défile, les deux premières cartes s'affichent (classe visible écrite dans le HTML), les quatre autres restent invisibles et la ligne vide. Sans IntersectionObserver (Safari < 12.1), new IntersectionObserver lève une ReferenceError avant que l'écouteur scroll ne soit posé : ni cartes ni ligne. Sans overflow: hidden auto (Chrome < 68, Firefox < 61), le bloc s'étire à la hauteur de son contenu et rien ne défile.

Le code

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

index.html — structure
<div class="demo-preview">
  <div class="scroll-container" id="timeline-scroll">
    <div class="timeline-inner">
      <div class="timeline-line"><div class="timeline-line-fill" id="timeline-fill"></div></div>
      <div class="timeline-spacer"></div>
      <div class="timeline-item" data-timeline>
        <div class="timeline-dot"></div>
        <div class="timeline-year">…</div>
        <div class="timeline-title">…</div>
        <div class="timeline-desc">…</div>
      </div>
      <!-- × 6 -->
      <div class="timeline-spacer"></div>
      <div class="timeline-spacer"></div>
    </div>
  </div>
  <span class="scroll-hint-label">…</span>
</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
Hauteur du bloc (CSS — .scroll-container { height: 280px }) 280 px Fixe la fenêtre et donc la course (contenu − hauteur : 419 px ici). Plus haut, plus de cartes révélées au chargement (3 sur 6 à 280 px) ; la pointe de la ligne se trouve toujours à 20 + (hauteur − 40) × r px du haut du cadre.
Seuil de révélation (JS — threshold: .4) .4 (40 % de la carte dans le cadre, soit 31,9 px) 0 révèle au premier pixel, 1 exige la carte entière — à 280 px de haut, la dernière ne l'est qu'en bout de course. Ajoutez rootMargin: '0px 0px -20% 0px' pour révéler plus tard sans toucher au seuil.
Aller simple (JS — e.isIntersecting && e.target.classList.add("visible")) jamais retiré Ajoutez else e.target.classList.remove("visible") pour un rendu bidirectionnel (c'est fx-0577). Pour libérer l'observateur une fois la carte révélée : observer.unobserve(e.target) dans la branche vraie — le code vendu n'observe jamais moins de six cartes.
Entrée des cartes (CSS — .timeline-item { transition: .5s cubic-bezier(.16, 1, .3, 1) } et translateX(20px)) .5 s très amortie (72 % à 100 ms), 20 px de glissement .25s quand plusieurs cartes entrent d'un coup lors d'un défilement rapide ; ease-out pour une entrée plus linéaire ; translateX(0) pour un fondu seul, translateY(16px) pour une montée.
Traînée de la ligne (CSS — .timeline-line-fill { transition: height .15s linear }) 150 ms linéaire Lisse les à-coups de la molette ; mesuré, la pointe atteint sa cible 163 ms après un saut. 0s pour une ligne collée au défilement, .4s pour une encre qui court derrière le pouce.
Couleurs (CSS — dégradé #6366f1, #a855f7, #ec4899 ; point et halo #6366f1 / rgba(99,102,241,.4) ; année #a855f7) indigo → violet → rose Le dégradé est peint sur le remplissage, il se dévoile donc du haut vers le bas ; deux couleurs suffisent, ou un arrêt intermédiaire à la hauteur d'un jalon clé (scène 3 : vert, puis ambre à l'écart). Le halo reprend la couleur du point à 40 %.
Étendue de la ligne (CSS — .timeline-line { top: 20px; bottom: 20px } et les .timeline-spacer de 60 px) tout le contenu : 69 px au-dessus du premier point, 191 px sous le dernier Avec les deux espaceurs finaux, 100 % arrive bien après la dernière carte. Posez top et bottom aux centres du premier et du dernier point (scènes de cette page) pour que la ligne pleine signifie « dernière étape ».
Position du point (CSS — .timeline-dot { left: -23px }) −23 px (centre à 22 px, ligne à 25 px) Le point est 3 px à gauche de la ligne. left: -20px le centre exactement ; si vous changez le padding-left de .timeline-inner (40 px) ou le left de la ligne (24 px), recalculez : left du point = ligne + 1 − 5 − marge interne de la carte (20 px).

FAQ

Telle quelle, la hauteur du remplissage est le ratio de défilement : 50 % de ligne pleine ne signifie pas trois étapes sur six. Mesuré, la pointe dépasse les six points à 10,5, 22,6, 34,7, 46,8, 58,9 et 71 % de la course — puis remplit 191 px de ligne vide. Pour une lecture métier (« étape 3 sur 5 »), remplacez le calcul de l'écouteur par la position du jalon courant : timelineFill.style.height = (item.offsetTop + 9 − 20) + 'px' (centre du point moins le top de la ligne) — la ligne devient indépendante du scroll et vous gardez l'observateur pour l'entrée des cartes. Entre-deux retenu dans les scènes de cette page : garder le ratio, mais tendre la ligne du centre du premier point à celui du dernier (top et bottom ajustés) pour que 0 % et 100 % coïncident avec la première et la dernière étape, et faire lire le même ratio par une colonne qui compte les points dépassés par la pointe.
Trois choses, dans le code. fx-0577 retire la classe visible quand la carte sort (isIntersecting ? add : remove, seuil .3) : ses blocs se rejouent à chaque passage, ceux de fx-0582 restent (mesuré : remonté en haut après une course complète, visible = 111111 et ligne à 0 %). fx-0582 ajoute la ligne et son écouteur scroll, que fx-0577 n'a pas. Et l'entrée diffère : 20 px de glissement horizontal en 500 ms contre 30 px vertical en 600 ms. Choisissez fx-0577 pour une page marketing où chaque section doit revivre, fx-0582 quand ce qui a été vu doit rester — historique, procédure, traçabilité — et que la proportion parcourue compte.
Pas en collant le code deux fois : les trois const sont globales et le second script s'arrête sur SyntaxError: Identifier 'timelineScroll' has already been declared (mesuré : la seconde chronologie ne se remplit ni ne révèle). Même enveloppé, le script vise un seul #timeline-scroll et observe tous les [data-timeline] de la page avec ce seul bloc pour root — les cartes d'un autre bloc ne sont jamais intersectantes. Enveloppez le code dans document.querySelectorAll('.scroll-container').forEach(zone => { const fill = zone.querySelector('.timeline-line-fill'), items = zone.querySelectorAll('[data-timeline]'); … }) : un observateur et un écouteur par bloc, ids retirés. C'est ainsi que les scènes 2 et 3 de cette page sont instanciées.