Scroll✨ Premium

Sticky Horizontal Reveal

Un cadre 16/10 qui défile sur 300 % de sa hauteur : un bloc sticky y reste épinglé et le JS convertit chaque pixel de scroll vertical en translation horizontale d'une rangée de 4 panneaux, barre de progression comprise. Les panneaux se mesurent sur le cadre (40 % de sa largeur, 230 px minimum), donc la rangée déborde toujours : mesuré, 626 px de course dans un cadre de 374 px, 670 px dans un cadre de 984 px. Aucune bibliothèque, aucun fichier externe.

ScrollStickyHorizontal

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

Roadmap produit par trimestres — page produit d'un éditeur SaaS — Sticky Horizontal Reveal exemple 1

① Roadmap produit par trimestres — page produit d'un éditeur SaaS

QuandPage « Produit » ou « Roadmap » d'un SaaS : quatre trimestres, chacun avec ses fonctionnalités, insérés dans une page qui se lit de haut en bas.
PourquoiLe temps se lit de gauche à droite mais la page se lit de haut en bas : la conversion d'axe met une frise horizontale dans le flux vertical sans imposer de geste latéral, et c'est le lecteur qui donne le rythme — Sticky Horizontal (fx-0578) ferait défiler les trimestres tout seul en boucle de 12 s, Horizontal Scroll (fx-0541) demanderait un balayage horizontal (scroll-snap-type: x) qui rompt la lecture. Une fois le cadre parcouru, la molette rend la page.
RéglagesQuatre panneaux de 250 px (rangée de 1 080 px) dans un cadre de 540 px — la largeur fixe de la scène remplace le max(230px, 40cqw) vendu — : 540 px de course pour deux hauteurs de cadre ; texte #0c1022 sur les dégradés vendus ; pastille réécrite « Scrollez ↓ pour parcourir les trimestres → » ; hauteur fixée à 296 px à la place de aspect-ratio.
Visite virtuelle d'un centre commercial — le plan avance sous vos pas — Sticky Horizontal Reveal exemple 2

② Visite virtuelle d'un centre commercial — le plan avance sous vos pas

QuandSite d'immobilier commercial : le plan d'un niveau, boutiques de part et d'autre d'une allée, un repère « Vous êtes ici » ; le visiteur scrolle vers le bas, le plan glisse vers la gauche.
PourquoiLa conversion vertical → horizontal est la marche elle-même : descendre la page devient avancer dans l'allée, et le repère qui reste fixe dans le bloc épinglé pendant que la rangée se déplace fait l'illusion. Zoom In Sticky (fx-0590) grossit un objet immobile ; seul cet effet déplace le point de vue, et la course exacte — largeur du plan moins largeur du cadre — garantit d'arriver pile à la sortie.
RéglagesUn seul élément de 1 500 px dans .shr-row à la place des panneaux (960 px de course dans 540 px), gap et padding à 0, cadre en pleine hauteur, repère posé dans .shr-sticky hors de la rangée ; spacer à 300 % conservé.
Timeline d'une mission spatiale — page pédagogique d'une agence — Sticky Horizontal Reveal exemple 3

③ Timeline d'une mission spatiale — page pédagogique d'une agence

QuandFiche de cours : les cinq phases d'une mission vers Mars (Lancement → Orbite → Transfert → Insertion martienne → Atterrissage) en panneaux, lues par un élève sur tablette ou ordinateur.
PourquoiUne mission se raconte dans l'ordre et de gauche à droite ; le scroll vertical, seul geste que l'élève connaît, fait avancer la ligne de vol d'un bloc — le plan entier se déplace, phases et trajectoire ensemble. Scroll Choreography Sequence (fx-0797) fait entrer et sortir des éléments chacun à son tour ; ici rien n'apparaît ni ne disparaît, on se déplace. Cinq panneaux au lieu de quatre : la course suit sans rien recalculer.
RéglagesCinq panneaux de 200 px (largeur fixée à la place du max(230px, 40cqw) vendu) encadrés par la Terre et Mars (rangée d'environ 1 230 px, 690 px de course dans 540 px), dégradés bleu nuit #1b2a6b → #0e1b4a à texte blanc, ligne de trajectoire en pointillés derrière la rangée (.shr-row::before), fond de panneau redéfini car le CSS vendu s'arrête à nth-child(4).

Comment ça marche

La structure emboîte cinq boîtes. .shr-scene (aspect-ratio: 16/10, overflow: hidden, isolation: isolate, fond #0c1022, arrondi 14 px) porte aussi tout ce qui rend le bloc autonome dans une page vierge : la couleur de texte, la pile de polices (Inter puis les polices système) et un box-sizing: border-box limité à ses descendants — il n'attend ni police, ni fond, ni reset de la page hôte. Il contient .shr-scroll, un conteneur absolu sur toute la scène en overflow-y: auto dont la barre est masquée (scrollbar-width: none + ::-webkit-scrollbar { width: 0 }). Dedans, .shr-spacer mesure 300 % de la hauteur : c'est lui qui crée la course verticale — mesuré à 984 px de large, scène de 615 px, spacer de 1 845 px, soit 1 230 px de scroll, exactement deux hauteurs de cadre. .shr-sticky (position: sticky; top: 0; height: 33.3333%, donc la hauteur du cadre) reste épinglé pendant que le spacer défile sous lui ; déclaré container-type: inline-size, il sert d'unité de mesure aux panneaux. Il contient .shr-row, une rangée flex en width: max-content (gap 16 px, padding 0 16 px) de quatre .shr-panel de max(230px, 40cqw) — 40 % de la largeur du cadre, 230 px au minimum — sur 78 % de la hauteur, chacun avec son dégradé à 135° ; la rangée fait donc 4 × 230 + 3 × 16 + 2 × 16 = 1 000 px dans un cadre de 374 px, et 4 × 394 + 80 = 1 654 px dans un cadre de 984 px. Deux calques au-dessus (z-index: 4, pointer-events: none) : la pastille .shr-hint « Scrollez ↓ pour avancer → » et la barre .shr-prog de 3 px dont le remplissage vaut calc(var(--shr-p) * 100%).

Le JS ne fait qu'une chose, sans boucle. Pour chaque .shr-scene, il lit d'abord prefers-reduced-motion : si la préférence est active, il pose la classe shr-reduced sur la scène et s'arrête là, le CSS prend le relais (voir l'accessibilité). Sinon il écoute scroll sur .shr-scroll (passive: true) et resize sur window, et update() calcule p = scrollTop / (scrollHeight − clientHeight) puis travel = row.scrollWidth − sticky.clientWidth, la largeur de rangée qui dépasse du cadre. Il écrit --shr-x = p × travel (arrondi au dixième) sur la rangée, que le CSS applique en translate3d(calc(var(--shr-x) * -1px), 0, 0) avec will-change: transform — le déplacement reste sur le compositeur —, --shr-p = p (quatre décimales) sur la barre, et masque la pastille dès p > 0.04 (transition d'opacité de 0,3 s). Pas de requestAnimationFrame, pas de lissage : mesuré, 0 appel de rAF au repos et une position qui suit le scroll 1:1. Dans un cadre de 374 px (234 px de haut), un cran de molette de 100 px déplace la rangée de 134 px : la course horizontale de 626 px se fait en 467 px de scroll, ratio 1,34 ; dans un cadre de 984 px (615 px de haut), 670 px de course pour 1 230 px de scroll, ratio 0,54 — la traversée ralentit avec la hauteur du cadre. La fin de course tombe juste : au maximum, le bord droit du dernier panneau est à 16 px du bord du cadre, c'est le padding. Comme .shr-scroll est un conteneur de défilement natif, le chaînage joue : mesuré, quatre crans de 120 px remplissent le cadre de 374 px, le cinquième fait défiler la page (window.scrollY = 120), et Chromium comme Firefox donnent le focus au conteneur au Tab (ArrowDown = 40 px, PageDown = 204 px, soit 87,5 % du cadre), WebKit non.

L'amplitude ne dépend que d'une soustraction, largeur de rangée moins largeur du cadre, et c'est pour cela que les panneaux ne sont pas en largeur fixe. Avec quatre panneaux de 230 px, la rangée ferait 1 000 px et il ne resterait que 16 px de course dans un cadre de 984 px : tout serait déjà visible au repos, rien à révéler. .shr-sticky est donc déclaré container-type: inline-size et chaque panneau mesure max(230px, 40cqw) : 40 % de la largeur du cadre, jamais moins de 230 px. Quatre panneaux occupent ainsi 160 % du cadre plus les gaps et le padding, soit une course d'environ 60 % de sa largeur plus 80 px, quelle que soit la colonne où le bloc est collé — mesuré : 626 px dans 374 px (panneaux à 230 px, le minimum), 670 px dans 984 px (panneaux à 394 px), 838 px dans 1 264 px (panneaux à 506 px). La déclaration width: 230px qui précède est le repli des navigateurs sans container queries (avant 2023) : ils ignorent la seconde ligne et gardent des panneaux fixes — l'effet reste juste dans un cadre étroit, mais n'a plus rien à révéler dès que la rangée tient dans le cadre. Le reste du code vendu se limite à l'effet : un seul IIFE d'environ 1,4 Ko, aucune bibliothèque, aucune image, aucune police ni aucun fichier à héberger ; fond, couleur de texte et pile de polices sont déclarés sur .shr-scene, et le box-sizing ne touche que ses descendants, jamais le reste de votre page.

Accessibilité

  • prefers-reduced-motion active un repli sans conversion d'axe : le script lit la préférence et, si elle est active, pose la classe shr-reduced sur la scène avant de se retirer, sans attacher le moindre écouteur. Le CSS prend le relais : spacer ramené à 100 % (plus rien à faire défiler verticalement), .shr-sticky en overflow-x: auto avec la transformation annulée, pastille et barre de progression masquées. Mesuré en mouvement réduit dans un cadre de 1 264 px : course verticale nulle, rangée de 2 102 px en défilement horizontal natif de 838 px, barre de défilement du navigateur (permanente ou affichée pendant le geste selon le système), dernier panneau atteignable à la molette, au doigt ou aux flèches. Rien ne bouge tout seul et rien n'est converti : la personne fait défiler une rangée, comme n'importe quelle liste horizontale. Pour forcer ce mode partout, posez la classe dans le HTML : le script la respecte.
  • Contraste des textes sur les dégradés vendus (pixels relevés sous le texte, Chromium) : le libellé .n (12 px, blanc à 85 %) mesure de 1,6:1 à 2,55:1 sur les quatre dégradés, seuil 4,5:1 ; le titre h4 (19 px gras, seuil 3:1 pour du texte large) de 1,32:1 sur « Recherche » à 3,1:1 sur « Design » — un seul passe, de justesse. Les dégradés clairs sont ceux de l'effet ; si vos panneaux portent du texte à lire, passez-le en #0c1022 (le fond de la scène) : mesuré, les mêmes textes tiennent alors de 6,1:1 à 14,3:1 sur les quatre panneaux — .shr-panel { color: #0c1022 }, c'est ce que fait la scène « roadmap » ci-dessous.
  • Clavier et défilement : .shr-scroll est un div à overflow-y: auto. Chromium et Firefox le rendent focalisable au clavier (mesuré : Tab s'y arrête, ArrowDown = 40 px, PageDown = 204 px sur 467), Safari non (mesuré dans WebKit : Tab reste sur le corps de la page, PageDown et ArrowDown sans effet) — ajoutez tabindex="0", role="region" et un aria-label qui dit comment avancer, car la pastille est aria-hidden et la barre de défilement est masquée : ce sont les deux seuls indices visuels, et la pastille s'efface après 4 % de la course (19 px dans un cadre de 374 px). Au doigt, le conteneur natif fonctionne, mais il retient le geste sur deux hauteurs de cadre avant de rendre la page (chaînage mesuré à la molette : quatre crans de 120 px pour le cadre, le cinquième pour la page).
  • Lecteurs d'écran : les quatre panneaux sont du texte dans l'ordre du DOM, rien n'est masqué ni réordonné (une transformation ne cache rien à l'arbre d'accessibilité), .shr-hint et .shr-prog sont aria-hidden, et le script ne touche qu'à des styles : aucune annonce parasite. Deux points d'intégration : les titres sont des h4 — alignez le niveau sur votre plan de titres — et la progression n'existe que visuellement ; si elle compte, exposez-la (role="progressbar" avec aria-valuenow mis à jour dans update()).

Compatibilité navigateur

Requiert position: sticky, aspect-ratio, inset, les variables CSS dans calc() et translate3d ; scrollbar-width n'est lu que par Firefox 64+, Chrome 121+ et Safari 18.2+, mais ::-webkit-scrollbar { width: 0 } couvre les WebKit et Blink antérieurs. La largeur des panneaux en cqw demande les container queries (Chrome 105+, Safari 16+, Firefox 110+) ; avant, la déclaration width: 230px qui la précède s'applique et l'effet fonctionne avec des panneaux fixes. JavaScript ES5 avec écouteurs passive et matchMedia, aucune bibliothèque, aucun fichier à héberger. Le défilement est converti par le script, pas par animation-timeline : contrairement à Horizontal Scroll Section (fx-0567), Horizontal Scroll (fx-0564) ou Zoom In Sticky (fx-0590), pilotés en CSS natif, cet effet fonctionne dans Firefox, qui ne prend en charge les animations pilotées par le scroll dans aucune version.

Chrome 88+✓ Complet (panneaux fixes à 230 px avant Chrome 105)
Firefox 89+✓ Complet (panneaux fixes à 230 px avant Firefox 110)
Safari 15+✓ Complet (panneaux fixes à 230 px avant Safari 16)
Edge 88+✓ Complet (panneaux fixes à 230 px avant Edge 105)
Mobile iOS 15+✓ Complet (le doigt fait défiler le cadre)
Android Chrome✓ Complet (le doigt fait défiler le cadre)

Sans JavaScript, le cadre défile bien sur ses trois hauteurs mais la rangée ne bouge pas : le bloc épinglé montre les premiers panneaux, la pastille reste, les autres panneaux sont inatteignables. Si vos pages doivent vivre sans script, posez la classe shr-reduced dans le HTML : le repli CSS (rangée en défilement horizontal natif) ne dépend pas du JS. Sans aspect-ratio (navigateurs d'avant 2021), .shr-scene n'a aucune hauteur propre et le conteneur absolu se retrouve dans une zone de taille nulle (mesuré avec aspect-ratio: auto : scène et conteneur à 0 px) : donnez-lui une height explicite. Sous prefers-reduced-motion, le script pose lui-même la classe shr-reduced (voir l'accessibilité).

Le code

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

index.html — structure
<div class="shr-scene">
  <div class="shr-hint" aria-hidden="true">Scrollez ↓ pour avancer →</div>
  <div class="shr-prog" aria-hidden="true"><i></i></div>
  <div class="shr-scroll">
    <div class="shr-spacer">
      <div class="shr-sticky">
        <div class="shr-row">
          <div class="shr-panel">
            <span class="n">ÉTAPE 01</span>
            <h4>Recherche</h4>
          </div>
          <div class="shr-panel">
            <span class="n">ÉTAPE 02</span>
            <h4>Design</h4>
          </div>
          <div class="shr-panel">
            <span class="n">ÉTAPE 03</span>
            <h4>Build</h4>
          </div>
          <div class="shr-panel">
            <span class="n">ÉTAPE 04</span>
            <h4>Lancement</h4>
          </div>
        </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
Nombre et largeur des panneaux (HTML — 4 × .shr-panel ; CSS — width: max(230px, 40cqw)) 4 panneaux à 40 % du cadre, 230 px minimum (rangée de 1 000 px dans 374 px, 1 654 px dans 984 px) La course vaut largeur de rangée − largeur du cadre : à 40 %, quatre panneaux débordent toujours d'environ 60 % du cadre plus 80 px, et chaque panneau ajouté allonge la course de sa largeur plus 16 px de gap. Une largeur fixe (230px) redonne une course qui fond à mesure que le cadre s'élargit. Un cinquième panneau n'a pas de fond : les dégradés s'arrêtent à nth-child(4).
Longueur de la course verticale (CSS — .shr-spacer { height: 300% } et .shr-sticky { height: 33.3333% }) 300 % → deux hauteurs de cadre Les deux valeurs sont couplées : sticky = 100 / N %. 200% / 50% = traversée en une hauteur, plus vive ; 500% / 20% = quatre hauteurs, plus lente. Le ratio horizontal/vertical vaut course ÷ ((N − 1) × hauteur du cadre).
Format du cadre (CSS — .shr-scene { aspect-ratio: 16/10 }) 16/10 (374 × 234 px sur mobile, 984 × 615 px sur bureau) La hauteur suit la largeur, et la course horizontale aussi (40 % du cadre par panneau) : un cadre plus large donne des panneaux plus larges et une traversée plus lente, puisque la course verticale (deux hauteurs de cadre) grandit avec lui. Dans une page, remplacez par une height fixe pour ne pas dépendre de la largeur de colonne.
Disparition de la pastille (JS — p > 0.04 ; CSS — transition: opacity .3s) 4 % de la course (19 px sur 467) Seuil de scroll au-delà duquel « Scrollez ↓ pour avancer → » s'efface. 0.15 la garde plus longtemps ; le texte se change dans le HTML.
Barre de progression (CSS — .shr-prog, --shr-p) 3 px, blanc sur blanc à 14 %, à 12 px du bas Alimentée par --shr-p à quatre décimales. Supprimez le bloc pour l'enlever (le JS vérifie if (prog)), ou lisez la variable ailleurs, pour un compteur par exemple.
Couleurs des panneaux (CSS — .shr-panel:nth-child(1…4), color: #fff) 4 dégradés à 135° (bleu → menthe, rose → violet, orange → rose, cyan → lavande), texte blanc Texte blanc mesuré de 1,3:1 à 3,1:1 : passez en #0c1022 (6,1 à 14,3:1) ou assombrissez les dégradés.
Précision de la translation (JS — toFixed(1)) 0,1 px Sous-pixel, lissé par translate3d. toFixed(0) cale sur le pixel entier — texte plus net pendant le mouvement, sans coût supplémentaire.
Repli mouvement réduit (CSS — .shr-scene.shr-reduced ; JS — classList.add sous prefers-reduced-motion) classe posée par le script quand la préférence est active Rangée en défilement horizontal natif, transformation annulée, pastille et barre masquées. Posez la classe dans le HTML pour forcer ce mode partout (il fonctionne aussi sans script), ou retirez la ligne classList.add pour garder la conversion d'axe même en mouvement réduit — déconseillé.

FAQ

Parce que la course est une soustraction : row.scrollWidth − sticky.clientWidth. Avec quatre panneaux fixes de 230 px, la rangée fait 1 000 px (4 × 230 + 3 × 16 + 2 × 16) et il ne resterait que 16 px à révéler dans un cadre de 984 px, tout étant déjà visible au repos. C'est pourquoi .shr-sticky est un conteneur (container-type: inline-size) et chaque panneau mesure max(230px, 40cqw) : la rangée déborde toujours du cadre d'environ 60 % de sa largeur plus 80 px — mesuré, 670 px dans 984 px et 838 px dans 1 264 px. Si vous fixez la largeur (width: 230px, ou des panneaux à 45vw), visez une rangée d'au moins deux fois la largeur du cadre pour que la traversée se sente ; en dessous, le script n'a pas de bug, il n'a simplement rien à révéler. Même logique dans un navigateur sans container queries (avant 2023) : il retombe sur 230px.
Les deux emboîtent un spacer, un bloc sticky et une rangée, mais fx-0567 anime la rangée en CSS natif — animation-timeline sur un @keyframes de translateX(0) à translateX(-75%) — quand fx-0796 la déplace en JavaScript. Trois conséquences. Firefox : fx-0567 y reste immobile, les animations pilotées par le scroll n'y existent dans aucune version ; fx-0796 y fonctionne. Fin de course : −75 % est une valeur fixe qui ne tombe juste que pour un nombre de panneaux donné ; ici la course est calculée sur la largeur réelle et s'arrête sur le dernier panneau quel qu'en soit le nombre. Réutilisation : --shr-x et --shr-p sont écrites dans le DOM, une barre de progression ou un compteur peuvent les lire. Les autres voisins sont plus loin : Sticky Horizontal (fx-0578) est une boucle CSS de 12 s sans aucune interaction, Horizontal Scroll (fx-0541) un vrai défilement latéral à scroll-snap.
Oui, et c'est l'usage « section épinglée » du storytelling. Le code vendu enferme tout dans .shr-scroll, un conteneur de défilement de la taille de la scène : pratique en carte, mais il retient la molette et le doigt sur deux hauteurs avant de rendre la page (mesuré : quatre crans de 120 px pour le cadre, le cinquième fait défiler la page). Pour une section dans le flux : supprimez .shr-scroll, faites de .shr-spacer une section de 300vh et de .shr-sticky un bloc de 100vh, puis dans update() remplacez scrollTop / max par −rect.top / (rect.height − innerHeight) avec rect = spacer.getBoundingClientRect(), borné entre 0 et 1, et écoutez scroll sur window. Le reste — travel, --shr-x, la barre — ne change pas.