Scroll✨ Premium

Horizontal Scroll Section

Six panneaux de 200 px sur une piste épinglée dans une zone de 280 px qui défile sur 840 px : une timeline nommée (scroll-timeline: --hscrollTimeline) translate la piste de −75 % de sa propre largeur — −984 px pour 1 312 px, mesuré linéaire — sans pixel codé en dur ni JavaScript ; Firefox, qui n'anime pas au défilement, reçoit la piste à plat par un repli @supports.

CSSscroll-timelineHorizontal

Mis à jour le

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 de fabrication d'un fromage AOP — site d'une fromagerie — Horizontal Scroll Section exemple 1

① Étapes de fabrication d'un fromage AOP — site d'une fromagerie

QuandPage « De la traite à l'affinage » : six panneaux (traite, caillage, moulage, salage, affinage, expédition) qui passent de droite à gauche pendant que le visiteur descend.
PourquoiLe fromager ajoutera une étape l'an prochain : la translation en % s'ajuste à la largeur de la piste (mesuré, un septième panneau arrive entier en bout de course), alors que fx-0564 laisserait les panneaux ajoutés hors champ avec ses −600 px fixes. Et la lecture de gauche à droite est celle d'un procédé : chaque étape chasse la précédente.
RéglagesZone de 304 px sous un en-tête de 56 px — .hscroll-outer et .hscroll-sticky à la même hauteur, spacer conservé à 400 % (course 912 px) — et panneaux de 341 × 218 px : la piste fait 2 158 px, quatre fois le cadre de 540 px, et les −75 % du code calent le dernier panneau au bord droit. Fond crème, cartes papier, titres en serif, jauge d'affinage qui passe de 14 à 100 % d'un panneau à l'autre ; tabindex="0" et aria-label sur la zone.
Composition d'un train de nuit, voiture par voiture — page de réservation d'une compagnie ferroviaire — Horizontal Scroll Section exemple 2

② Composition d'un train de nuit, voiture par voiture — page de réservation d'une compagnie ferroviaire

QuandPage « À bord » : les voitures (locomotive, couchettes, cabines, voiture-bar, sièges inclinables, fourgon) défilent horizontalement comme le train, au geste vertical du client.
PourquoiUn train se lit de gauche à droite : la piste épinglée le fait avancer au geste vertical du client — fx-0541 Horizontal Scroll (publié) est un défilement horizontal natif qui exige de glisser latéralement, peu naturel à la molette. Ici la molette, la flèche bas ou le pouce font avancer le train, et remonter le fait reculer.
RéglagesZone de 308 px sous un en-tête de 52 px, voitures de 341 × 150 px : roues en ::before et ::after (le code vendu ne touche pas ces pseudo-éléments), fenêtres en une rangée de i, locomotive distinguée par une classe plutôt que par :nth-child(1), rail fixe posé hors de la zone pour que les roues roulent dessus ; gap ramené à 14 px, piste de 2 148 px.
Plan de l'orchestre, pupitre par pupitre — page « Découvrir l'orchestre » d'une philharmonie — Horizontal Scroll Section exemple 3

③ Plan de l'orchestre, pupitre par pupitre — page « Découvrir l'orchestre » d'une philharmonie

QuandPage « Découvrir l'orchestre » : premiers violons, seconds violons, bois, cuivres, percussions, cordes graves — six pupitres qui défilent de gauche à droite dans l'ordre où ils sont placés sur scène, de jardin à cour, vus depuis la salle, pendant que l'abonné descend.
PourquoiUn plan de scène est une disposition, pas une suite : l'abonné va des premiers violons aux percussions, revient comparer cors et trompettes, et la piste épinglée suit son geste dans les deux sens — molette, pouce ou flèches — puis tient les cordes graves en bout de course (animation-fill-mode: both). Le bloc collant ne bouge pas pendant que les pupitres glissent : le bord de scène — jardin, chef d'orchestre, cour — reste un repère fixe sous les cartes, et c'est lui qui donne la position. La timeline nommée sur la zone permet deux plans sur la même page, orchestre symphonique et orchestre de chambre, chacun piloté par son propre défilement (mesuré : deux copies indépendantes), et dans Firefox le repli @supports couche les pupitres à plat, de jardin à cour — un plan qui se lit toujours comme un plan.
RéglagesZone de 302 px sous un en-tête de 58 px, panneaux de 341 × 200 px à liseré gauche de 5 px à la couleur de la famille — cordes, bois, cuivres, percussions, variable --fam posée par :nth-child — avec la place sur scène, l'effectif, une phrase, le voisin de pupitre et un plan de scène miniature de 54 × 32 px dont la zone du pupitre est un ::before placé par quatre variables ; bord de scène (jardin, chef d'orchestre, cour) en absolu dans le bloc épinglé, sous la piste, donc immobile pendant que les cartes glissent ; fond clair, texte à 12 px sur blanc.

Comment ça marche

Quatre boîtes emboîtées et une animation. La zone .hscroll-outer (100% × 280px, overflow: hidden auto) est le conteneur qui défile et déclare la timeline nommée : scroll-timeline: --hscrollTimeline y. Dedans, .hscroll-spacer fait 400% de la zone, 1 120 px : la course vaut 1 120 − 280 = 840 px. Le bloc .hscroll-sticky (position: sticky; top: 0, 280 px, overflow: hidden) reste épinglé en haut de la zone toute la course (mesuré : top à 0 de bout en bout), et la piste .hscroll-track — flex, gap: 16px, padding: 20px 16px, width: max-content, 1 312 px pour six panneaux de 200 px — porte l'animation scrollHorizontal (translateX(0) → translateX(-75%)) avec animation-timeline: --hscrollTimeline, animation-duration: auto, linear et animation-fill-mode: both. Mesuré dans Chromium par crans de 210 px : −246, −492, −738, −984 px à 840, soit 1,17 px de piste par pixel défilé, linéaire ; molette en arrière, la piste revient à 0. La timeline nommée se résout vers l'ancêtre le plus proche qui la déclare : deux copies collées dans la même page sont indépendantes (mesuré : molette sur la seconde, −492 px pour elle, 0 pour la première). Champ JavaScript vide, aucune erreur console, aucune variable du site.

Le -75% est le cœur de l'effet : un pourcentage de translateX se rapporte à la largeur de l'élément transformé, donc à la piste. Ajoutez un panneau, la piste passe à 1 528 px et la translation à −1 146 px sans toucher au CSS (mesuré : septième panneau entier de 166 à 366 px en bout de course). C'est ce qu'une valeur en pixels ne fait pas — Horizontal Scroll (fx-0564) fixe -600px. L'envers : ce qui reste visible à la fin est toujours le dernier quart de la piste, 328 px, quelle que soit la largeur du cadre — à 1 264 px de large, 952 px restent vides à droite (voir la FAQ). Les trois scènes de cette page donnent donc à la piste quatre fois la largeur du cadre : panneaux de 341 px pour 540 px, et le dernier panneau s'arrête au bord.

Le code vendu a été corrigé sur deux points. La zone était en height: 100% : dans un parent sans hauteur fixe (.demo-preview n'a qu'un min-height), ce pourcentage retombe sur auto, la zone prend la hauteur de son contenu, la course vaut 0 et la piste ne bouge jamais — mesuré collé dans une page vierge à 1 280 et 390 px, et dans l'aperçu du site lui-même. Elle est désormais à 280px, la hauteur du bloc épinglé. Et un bloc @supports not (animation-timeline: scroll()) a été ajouté pour les navigateurs sans animations pilotées par le défilement, Firefox en tête (voir la compatibilité). Reste un élément étranger : l'indice .slr-scroll-hint « Scrollez verticalement » (préfixe d'un autre effet, français en dur, 11,2 px, blanc à 40 %, rebond de 4 px toutes les 2 s), en position: absolute à 8 px du bas de .demo-preview — hors de ce cadre, il tombe au bas de la page.

Accessibilité

  • prefers-reduced-motion absent du code : mesuré sous émulation, rien ne change — −984 px en bout de course, indice en rebond (animation-play-state: running). Le mouvement horizontal ne se produit qu'à la demande du lecteur, mais WCAG 2.3.3 vise aussi les animations déclenchées par l'interaction : ajoutez @media (prefers-reduced-motion: reduce) { .slr-scroll-hint { animation: none } } et, pour supprimer la conversion d'axe, reprenez dans cette media query les trois règles du bloc @supports not (spacer en auto, sticky en static + overflow-x: auto, animation: none) : la piste se lit alors à plat, en défilement horizontal natif.
  • Clavier : mesuré dans Chromium, Tab donne le focus à la zone sans tabindex (anneau outline: auto 1px) ; flèche bas avance de 40 px (piste à −46,9 px), Page suivante de 285 px (−334 px), Fin va au bout (−984 px), Début revient à 0. Firefox et Safari ne focalisent pas une zone défilante sans tabindex="0" : ajoutez-le sur .hscroll-outer, avec un aria-label et un style :focus-visible. Au doigt, le geste doit commencer dans les 280 px de la zone ; overscroll-behavior: contain évite d'entraîner la page en butée.
  • Contraste (texte blanc de 19,2 px en graisse 700 : texte large, seuil 3:1) : les panneaux 1 (4,5:1 et 4,2:1) et 6 (5,7:1 et 4,7:1) tiennent, le 2 de justesse (3,5:1 et 3,7:1) ; les 3, 4 et 5 échouent sur leur première couleur — 2,4:1 sur #06b6d4, 2,2:1 sur #f59e0b, 2,5:1 sur #10b981. Assombrissez ces teintes de départ ou posez une ombre portée sur le texte. L'indice à 40 % de blanc vaut 3,8:1 pour 11,2 px : sous 4,5:1.
  • Lecteurs d'écran et repli : six div sans titre, lus dans l'ordre du DOM quelle que soit la position de la piste — rien n'est masqué à la synthèse vocale, mais rien ne structure non plus : donnez un titre (h3 ou h4) à chaque panneau et un aria-label à la zone. L'indice « Scrollez verticalement » est lu en français quelle que soit la langue de la page : aria-hidden="true". Dans le repli sans animation-timeline, c'est le bloc .hscroll-sticky qui défile horizontalement : il a besoin du même tabindex="0" pour le clavier.

Compatibilité navigateur

CSS seul : scroll-timeline nommée, animation-timeline, animation-duration: auto, position: sticky, overflow: hidden auto (deux valeurs), width: max-content, flexbox, dégradés. Zéro JavaScript, zéro dépendance. Les animations pilotées par le défilement existent dans Chrome et Edge 115+, Safari et iOS 26 (mesuré dans le WebKit de Playwright : −984 px en bout de course) ; Firefox ne les exécute dans aucune version : mesuré dans Firefox 153, CSS.supports('animation-timeline: scroll()') renvoie faux et, sans repli, l'animation saute à son état final — piste figée à −984 px, panneaux 1 à 4 hors cadre, seuls le 6 et la moitié du 5 visibles. Le bloc @supports not (animation-timeline: scroll()) du code vendu prend alors le relais : mesuré, les six panneaux à leur place (16 à 1 296 px), zone sans course verticale, bloc .hscroll-sticky en position: static et overflow-x: auto — 48 px de course horizontale à 1 264 px de large, 938 px à 374 px, molette horizontale et Maj+molette fonctionnent. Un visiteur Firefox lit une rangée de panneaux qui glisse au doigt ou à la molette, sans piste épinglée.

Chrome 115+✓ Complet
Firefox (toutes versions)✓ Repli : piste à plat en défilement horizontal natif, rien n'est animé au défilement
Safari 26+✓ Complet
Edge 115+✓ Complet
Mobile iOS 26+✓ Complet (le doigt fait défiler la zone)
Android Chrome 115+✓ Complet

Avant Chrome/Edge 115 et Safari 26, le même bloc @supports not s'applique : piste à plat, comme dans Firefox (@supports existe depuis Chrome 28, Firefox 22 et Safari 9). Sans la forme à deux valeurs overflow: hidden auto (Chrome < 68, Firefox < 61, Safari < 12.1), la zone ne défile plus verticalement — mais ces versions n'ont pas animation-timeline non plus, le repli s'applique et rien n'est perdu. Sans position: sticky (avant 2017), le bloc remonte avec le plateau et sort de la zone après 280 px.

Le code

Structure HTML à coller dans votre page (le CSS + JS sont inclus avec Premium) :

index.html — structure
<div class="demo-preview">
  <div class="hscroll-outer">
    <div class="hscroll-spacer">
      <div class="hscroll-sticky">
        <div class="hscroll-track">
          <div class="hscroll-panel">…</div>
          <div class="hscroll-panel">…</div>
          <div class="hscroll-panel">…</div>
          <div class="hscroll-panel">…</div>
          <div class="hscroll-panel">…</div>
          <div class="hscroll-panel">…</div>
        </div>
      </div>
    </div>
  </div>
  <span class="slr-scroll-hint">…</span>
</div>
Débloquer le code complet — 9,90 € HT/mois

Le code complet HTML + CSS + JS de Horizontal Scroll Section, prêt à coller — et les 811 effets. Ou 299 € une fois, accès à vie.

Personnaliser

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

Option / propriétéDéfautEffet
Course (CSS — .hscroll-spacer { height: 400% }, .hscroll-outer { height: 280px }) plateau de 1 120 px, course 840 px (1,17 px de piste par pixel) La course vaut plateau − zone, trois fois la hauteur de la zone à 400 %. 600% donne 1 400 px de course et une piste deux fois plus lente sous la molette. Le bloc épinglé doit garder la hauteur de la zone : plus petit, une bande de plateau nu apparaît dessous ; plus grand, il est rogné.
Translation finale (CSS — @keyframes scrollHorizontal { to { transform: translateX(-75%) } }) −75 % de la piste (−984 px pour 1 312 px) Le pourcentage se rapporte à la piste : à la fin, son dernier quart reste visible (328 px), quel que soit le cadre. -50% arrête la piste à mi-chemin, sur les panneaux 4 à 6. Pour caler la fin sur le bord du cadre : translateX(calc(100cqw - 100%)) avec container-type: inline-size sur .hscroll-sticky (mesuré : dernier panneau à 16 px du bord, à 1 264 comme à 374 px de large).
Nombre et taille des panneaux (HTML + CSS — .hscroll-panel { width: 200px; height: 200px }, gap: 16px) 6 panneaux de 200 × 200 px, piste de 1 312 px Chaque panneau ajouté allonge la piste de 216 px et la translation de 162 px, rien à recalculer (mesuré à 7 : piste 1 528 px, −1 146 px). Un septième n'a pas de fond : ajoutez sa règle :nth-child(7). Pour une fin de course calée sur un cadre de largeur W avec six panneaux : largeur = (4 W − 112) ÷ 6, soit 341 px pour 540 px.
Progression (CSS — animation-timing-function: linear, animation-range: normal) linéaire sur toute la course ease-in-out fait démarrer et finir la piste doucement pour le même défilement. animation-range: 10% 90% laisse la piste immobile sur les 84 premiers et 84 derniers pixels de course, animation-fill-mode: both tenant les deux états — le temps de lire le premier panneau.
Fonds des panneaux (CSS — :nth-child(1) à (6)) six dégradés à 135° : indigo, rose, cyan, ambre, vert, violet Un dégradé par panneau, dans l'ordre du DOM ; remplacez-les par une image (background: url(…) center/cover) ou une couleur unie. Le texte blanc de 19,2 px passe sous 3:1 sur les teintes claires des panneaux 3, 4 et 5 : assombrissez la première couleur du dégradé.
Indice « Scrollez verticalement » (CSS — .slr-scroll-hint, @keyframes hintBounce) 8 px du bas de .demo-preview, 11,2 px, blanc à 40 %, rebond de 4 px toutes les 2 s Français en dur, préfixe d'un autre effet, ancré sur .demo-preview : hors de ce cadre, il descend au bas de la page. Supprimez le span et la @keyframes hintBounce avec lui, ou posez-le dans .hscroll-sticky (position: relative sur le bloc) pour qu'il suive la zone.
Repli sans animation-timeline (CSS — @supports not (animation-timeline: scroll())) piste à plat, défilement horizontal natif du bloc Pour montrer tous les panneaux d'un coup plutôt qu'une rangée à faire glisser : dans ce bloc, .hscroll-track { flex-wrap: wrap; width: auto; animation: none } et .hscroll-sticky { height: auto } — les six panneaux se rangent en grille. Pour un carrousel à crans : scroll-snap-type: x mandatory sur le bloc et scroll-snap-align: start sur les panneaux.

FAQ

Parce que translateX(-75%) se mesure sur la piste, pas sur le cadre : en bout de course, il en reste toujours le dernier quart, 328 px pour six panneaux de 200 px. Mesuré à 1 264 px de large : le panneau 6 occupe 112 à 312 px, la moitié du 5 le précède, et 952 px sont vides à droite — la piste a « trop » avancé pour ce cadre ; à 374 px, le même état laisse 62 px libres. La valeur est calibrée pour un cadre d'environ 328 px, celui de la carte du catalogue, où le dernier panneau arrive pile au bord. Deux corrections : dimensionner les panneaux pour que la piste fasse quatre fois le cadre (341 px pour 540 px, les scènes de cette page), ou écrire la fin de course en fonction du cadre, translateX(calc(100cqw - 100%)) avec container-type: inline-size sur .hscroll-sticky — mesuré, le dernier panneau s'arrête à 16 px du bord à 1 264 comme à 374 px, sans changer la course de défilement.
Même famille, quatre choix opposés. fx-0564 aligne cinq cartes de 180 px et les pousse de translateX(-600px), une valeur fixe : une sixième carte finit à 400 px du bord gauche, hors d'un cadre de 374 px, et la valeur est à recalculer à chaque carte ajoutée ; fx-0567 écrit -75% et absorbe un septième panneau sans rien changer (mesuré : entier, de 166 à 366 px). fx-0564 utilise la timeline anonyme animation-timeline: scroll(), qui prend le premier ancêtre défilant — emballez la piste dans une autre boîte à overflow: auto et c'est elle qui pilote ; fx-0567 nomme sa timeline sur la zone et la piste la retrouve à travers n'importe quel intermédiaire. fx-0564 épingle sa piste à top: 50% dans un plateau de 800 px (course 520) avec scroll-behavior: smooth ; fx-0567 épingle un bloc plein de 280 px à top: 0 sur 840 px, sans lissage, en animation-fill-mode: both. Ni l'un ni l'autre ne bouge dans Firefox ; seul fx-0567 y prévoit un repli. Sticky Horizontal Reveal (fx-0796) est le voisin le plus proche : même spacer, même bloc collant, même rangée, mais déplacée par un script — IIFE de 1,4 Ko, un écouteur scroll par scène, --shr-x réécrite dans le DOM à chaque cran. Prenez fx-0796 si la rangée doit bouger aussi dans Firefox, s'arrêter pile sur le dernier panneau quel qu'en soit le nombre (course calculée sur la largeur réelle de la rangée) ou alimenter une barre de progression (--shr-p) ; prenez fx-0567 pour une piste sans aucun JavaScript, pilotée par la feuille de style sans écouteur ni écriture dans le DOM, avec un repli Firefox déjà dans le CSS vendu. Les deux se passent de bibliothèque et d'image, acceptent plusieurs copies dans la même page et reculent avec la molette.
Oui, en remplaçant la timeline de défilement par une timeline de vue sur le plateau — mesuré sur une page de 600 px de contenu avant et après, fenêtre de 800 px. Neutralisez la zone (.hscroll-outer { height: auto; overflow: visible; scroll-timeline: none }, cadre .demo-preview en display: block), donnez au plateau la course en hauteurs d'écran et une timeline de vue (.hscroll-spacer { height: 400vh; view-timeline: --hsView block }), au bloc épinglé la hauteur de l'écran (.hscroll-sticky { height: 100vh }), et branchez la piste : .hscroll-track { animation-timeline: --hsView; animation-range: contain 0% contain 100% }. La plage contain couvre exactement la phase épinglée : mesuré, le bloc se fixe à 608 px de défilement et se libère à 3 008 (trois fois 100vh), la piste va de 0 à −984 px sur cet intervalle (−489 px à mi-course) et y reste ensuite grâce au fill-mode: both. Une scroll-timeline nommée sur html étalerait la course sur tout le document, contenu avant et après compris. Le repli @supports not reste valable tel quel.