Scroll✨ Premium

Scroll Snap Sections

Un conteneur de 280 px en scroll-snap-type: y mandatory et scroll-behavior: smooth, cinq sections de 280 px, et 342 octets de JavaScript qui allument le point Math.round(scrollTop / 280) à chaque scroll. Les cinq points de 6 px sont des indicateurs : le code vendu ne contient aucun écouteur de clic — l'accès direct à une section se rajoute en six lignes.

CSS Scroll SnapJavaScriptNavigation

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

Pitch de financement projeté en réunion — sauter à la slide Finance pendant les questions — Scroll Snap Sections exemple 1

① Pitch de financement projeté en réunion — sauter à la slide Finance pendant les questions

QuandUn fondateur présente cinq slides verticales (Problème, Solution, Marché, Équipe, Finance) sur un écran partagé ; pendant les questions, un investisseur demande les chiffres et il faut afficher la slide Finance sans repasser par les trois autres.
PourquoiLe conteneur ne s'arrête qu'à 0, 280, 560, 840 ou 1 120 px : une slide est toujours entière à l'écran, jamais coupée. La colonne de points, en position absolue sur la droite, dit à tout moment où l'on est ; avec les six lignes de clic ajoutées, un point devient l'accès direct que la séance de questions exige — Vertical Snap (fx-0593), en CSS pur, n'affiche ni témoin ni accès direct.
RéglagesSections alignées à gauche (align-items: flex-start, padding: 28px 150px 28px 28px) pour libérer la colonne de droite, où chaque point reçoit un libellé (:has(.active) pour éclairer le libellé courant) ; points portés à 8 px, en <button>, avec l'écouteur de clic ; compteur « Diapo n / 5 » alimenté par le même Math.round ; dégradés vendus conservés.
Formulaire d'intégration SaaS en quatre étapes — revenir corriger l'étape Équipe — Scroll Snap Sections exemple 2

② Formulaire d'intégration SaaS en quatre étapes — revenir corriger l'étape Équipe

QuandUn outil B2B enchaîne Compte, Équipe, Facturation et Lancement dans un panneau de 280 px ; depuis Facturation, l'utilisateur veut retoucher les invitations de l'étape Équipe sans perdre ce qu'il a saisi.
PourquoiChaque étape occupe exactement la hauteur du conteneur et l'accrochage mandatory interdit l'entre-deux : on est sur une étape ou sur la suivante, jamais entre. Le retour en arrière se fait par un clic sur le point de l'étape visée, en 280 ms ; le contenu des étapes n'est pas rechargé, il défile — les champs gardent leur valeur. Snap + Progress (fx-0597) ajoute une barre de progression que ce parcours de quatre étapes n'a pas besoin de montrer.
RéglagesColonne .snap-dots sortie du flux absolu (position: static, transform: none, gap: 16px) pour vivre dans une barre latérale avec libellés et numéros ; quatre sections au lieu de cinq (les dégradés 1 à 4 du code vendu) ; instance à ids propres, l'index calculé sur clientHeight plutôt que sur 280 ; ascenseur masqué (scrollbar-width: none), les points le remplacent.
Portail de candidature à un appel d'offres public — vérifier une section complétée — Scroll Snap Sections exemple 3

③ Portail de candidature à un appel d'offres public — vérifier une section complétée

QuandLa réponse à un marché public est découpée en cinq sections de même hauteur (Identité, Références, Offre technique, Prix, Pièces jointes) ; le soumissionnaire, arrivé à Prix, veut relire ses Références avant le dépôt.
PourquoiDes sections de hauteur identique et un accrochage strict conviennent à un formulaire administratif structuré : chaque section s'affiche entière, avec son statut (complété, en cours, à faire), et le sommaire de droite reflète la position sans ambiguïté. Un clic sur le point d'une section antérieure la remonte en moins d'une demi-seconde (464 ms mesurées pour trois sections), sans quitter la page ni perdre la saisie — Mixed Content Snap (fx-0596) donnerait des sections de tailles variables, incompatibles avec cette régularité.
RéglagesLes cinq dégradés vendus remplacés par cinq bleus sobres (.snap-section:nth-child(n)), points dans un rail « Sommaire » avec libellé et état sous chaque point, section en bloc de formulaire (numéro, titre, pastille de statut, trois lignes) ; instance à ids propres, écouteur de clic, indicateur pulsant retiré.

Comment ça marche

Anatomie : le wrapper .demo-preview (position: relative, min-height: 300px, flex centré, fond #0a0a0f, font-family: sans-serif) contient .snap-container#snap-scroll100% × 280px, overflow-y: auto, scroll-snap-type: y mandatory, scroll-behavior: smooth, ascenseur de 4 px stylé par ::-webkit-scrollbar — et cinq .snap-section de 280px en scroll-snap-align: start (colonne flex centrée, gap: 10px, padding: 24px), soit 1 400 px de contenu pour 1 120 px de course. Chaque section porte une icône .snap-icon de 48 px (SVG de 24 px, rayon 14 px), un .snap-title (1rem, 700) et un .snap-desc (.75rem, blanc à 40 %, max-width: 220px) ; les fonds sont cinq dégradés à 135° attribués par :nth-child(1) à (5) (#0f0f23 → #1a1040, #0f1a23 → #102a3e, #1a0f1a → #3e102a, #0f230f → #103e10, #23200f → #3e3510), l'icône reprenant la teinte de sa section (#818cf8, #22d3ee, #ec4899, #34d399, #fbbf24 sur fond à 20 %). À droite, .snap-dots#snap-dotsposition: absolute, right: 12px, top: 50%, colonne, gap: 6px, z-index: 10 — aligne cinq .st-snap-dot de 6px en blanc à 15 % (transition: .3s) ; la classe .active les passe en #a855f7, halo 0 0 8px rgba(168, 85, 247, .5), scale(1.3). En bas, .scroll-hint-label « Scrollez pour explorer » (.65rem, capitales, bottom: 8px) pulse entre 30 % et 70 % d'opacité toutes les 2 s (@keyframes pulseHint, infini).

L'accrochage est entièrement CSS ; le script ne fait qu'observer. Mesuré dans Chromium : un coup de molette de 60 ou 120 px entame le mouvement (21 puis 52 px à 120 ms) et revient à 0, un coup de 280 px passe à la section suivante en 280 ms ; à 1 120 px, un coup de 500 px ne fait plus rien. Avec mandatory, les seules positions de repos sont 0, 280, 560, 840 et 1 120 px : un scrollTop forcé à 140 est ramené à 0, à 141 il est poussé à 280. Le JS tient en une ligne utile : snapScroll.addEventListener('scroll', …) calcule s = Math.round(snapScroll.scrollTop / 280) et fait classList.toggle('active', o === s) sur chaque point — le point bascule donc à mi-course, quand le défilement lisse franchit 140 px, pas à l'arrivée. Un scrollTo programmé met 280 ms pour une section, 464 ms pour trois (840 px) et 540 ms pour toute la course (1 120 px). L'écouteur est unique et porté par le conteneur : il n'y a rien à démonter, il disparaît avec l'élément. Le clavier fonctionne sans code : Tab donne le focus au conteneur (Chromium comme Firefox rendent les zones défilantes focalisables), ↓ mène à 280 px, PageDown à 560, End à 1 120, et les points suivent ; un balayage tactile mesuré en émulation mobile arrive à 280 px, point 2 allumé.

Ce que le code ne fait pas, mesuré : un clic au centre du 4e point ne déplace rienscrollTop 0 → 0, point actif inchangé. Les points sont des div de 6 × 6 px, cursor: auto, sans role ni tabindex, et le mot click n'apparaît pas dans le script : ce sont des témoins, comme le dit la fiche catalogue (« indicateur de pagination latéral »). Le 280 du JS n'est pas lu dans le DOM : avec des sections passées à 360 px, la section 3 allume le point 4, la 4 le point 5, la 5 n'en allume aucun. Le CSS vendu contient 18 règles étrangères sur 43 (1 489 octets sur 4 641, 32 %) : .sn-progress-dot.active (le point de Snap + Progress, fx-0597), .sticky-text-block.active, .split-screen-content.active, .snap-nav-dot.active — qui lit var(--primary), défini seulement dans le prélude .demo-preview —, puis 14 règles du customizer, des favoris et du panneau de code du site (.customizer-panel.active, .favorite-btn.active, .code-tab.active…). Aucune ne trouve d'élément chez vous : à supprimer, pas à corriger. Le script n'est pas isolé (un const snapScroll au niveau global) : collé deux fois dans une page, le second exemplaire meurt sur SyntaxError: Identifier 'snapScroll' has already been declared. Enfin, les titres vendus sont sans accents (« Rapidite », « Modularite », « Elegance », « Fiabilite ») alors que « sécurité » en a, et l'indicateur « Scrollez pour explorer » n'est jamais masqué : à 1 120 px il pulse encore (display: block, opacité mesurée .40) et chevauche de 10 px le bas du conteneur.

Accessibilité

  • prefers-reduced-motion absent du code. Mesuré en émulation : la requête média correspond, mais scroll-behavior reste smooth (11 px à 40 ms, 280 à l'arrêt), pulseHint tourne (animation-play-state: running) et la transition des points reste à .3 s. Ajoutez @media (prefers-reduced-motion: reduce) { .snap-container { scroll-behavior: auto } .scroll-hint-label { animation: none } .st-snap-dot { transition: none } } — l'accrochage lui-même n'est pas une animation, il peut rester.
  • Les points ne sont pas des commandes : div de 6 px, cursor: auto, ni role, ni tabindex, ni écouteur de clic. Si vous ajoutez la navigation (cf. Personnaliser), faites-en des <button type="button"> avec un aria-label (« Section 3 sur 5 »), aria-current="true" sur l'actif et une zone de clic d'au moins 24 px (WCAG 2.5.8) par padding ou pseudo-élément — 6 px, même ×1,3, reste une cible de 8 px. Contraste mesuré des points (composant d'interface, seuil 3:1) : inactif 1,53:1 sur la section 1, actif 4,77:1 sur la section 1 mais 3,08:1 sur la section 5 (fond ambre) ; passez l'inactif à 35 % de blanc.
  • Texte : .snap-desc est du blanc à 40 % en 12 px — mesuré 3,81:1 sur la section 1, 3,58:1 sur la 2, 3,34:1 sur la 5, sous le seuil de 4,5:1 (WCAG 1.4.3) ; à 60 % de blanc, toutes les sections dépassent 6:1. Les titres blancs mesurent 18,9:1. L'indicateur pulsant est illisible par construction (1,22:1 à 1,83:1 selon la phase) : gardez-le décoratif avec aria-hidden="true", ou rendez-le lisible et masquez-le à la dernière section.
  • Clavier et lecteurs d'écran : le conteneur reçoit le focus par Tab (mesuré dans Chromium et Firefox), les flèches, PageDown et End défilent et les points suivent ; posez tout de même tabindex="0" et un aria-label sur #snap-scroll pour les navigateurs qui ne focalisent pas les zones défilantes (Safari). Remplacez div.snap-title par un titre (h3) pour donner une structure aux cinq sections, mettez aria-hidden="true" sur les SVG et sur la colonne de points tant qu'elle n'est pas cliquable. Intégration : le parent de .snap-dots doit rester position: relative (c'est .demo-preview dans le code vendu), sinon la colonne se cale sur la fenêtre.

Compatibilité navigateur

Requiert ES2015 (const, fonctions fléchées), scroll-snap-type / scroll-snap-align (spécification 2018), scroll-behavior: smooth et box-shadow ; ::-webkit-scrollbar n'est que cosmétique. Rien ici n'est piloté au scroll en CSS natif (animation-timeline) : c'est de l'accrochage CSS classique et un écouteur JS, donc l'effet s'exécute intégralement dans Firefox — mesuré Firefox 153 : y mandatory appliqué, molette de 200 px → 280 px, point 2 allumé, zéro erreur. Zéro dépendance.

Chrome 69+✓ Complet
Firefox 68+✓ Complet (ascenseur natif, ::-webkit-scrollbar ignoré)
Safari 15.4+✓ Complet — 11 à 15.3 : accrochage sans défilement lisse
Edge 79+✓ Complet
Mobile iOS 15.4+✓ Complet (balayage)
Android Chrome✓ Complet (balayage mesuré : 280 px, point 2)

Sans JavaScript, l'accrochage et le défilement lisse fonctionnent (tout est CSS) mais le point actif reste figé sur le premier, la classe active étant écrite dans le HTML. Sans scroll-snap (navigateurs antérieurs à 2018), le conteneur défile librement et les points suivent quand même, Math.round désignant la section la plus proche.

Le code

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

index.html — structure
<div class="demo-preview">
  <div class="snap-container" id="snap-scroll">
    <div class="snap-section">
      <div class="snap-icon"><svg width="24" height="24" viewBox="0 0 24 24">…</svg></div>
      <div class="snap-title">Rapidite</div>
      <div class="snap-desc">…</div>
    </div>
    <div class="snap-section">…</div>
    <div class="snap-section">…</div>
    <div class="snap-section">…</div>
    <div class="snap-section">…</div>
  </div>
  <div class="snap-dots" id="snap-dots">
    <div class="st-snap-dot active"></div>
    <div class="st-snap-dot"></div>
    <div class="st-snap-dot"></div>
    <div class="st-snap-dot"></div>
    <div class="st-snap-dot"></div>
  </div>
  <span class="scroll-hint-label">Scrollez pour explorer</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 des sections (CSS — .snap-container et .snap-section height: 280px ; JS — / 280) 280 px, trois occurrences Les trois valeurs doivent bouger ensemble : mesuré avec 360 px en CSS et 280 dans le JS, la section 3 allume le point 4 et la section 5 n'en allume aucun. Remplacez le 280 du script par snapScroll.clientHeight et la hauteur devient libre — 100vh pour des sections plein écran.
Nombre de sections (HTML — .snap-section × 5, .st-snap-dot × 5) 5 Une section vaut un point ; le script compte les points, pas les sections. Les dégradés et les teintes d'icône s'arrêtent à :nth-child(5) : une sixième section n'a pas de fond (le #0a0a0f du wrapper apparaît), ajoutez ses deux règles.
Type d'accrochage (CSS — scroll-snap-type: y mandatory) y mandatory Aucune position de repos entre deux sections : un coup de molette de 120 px revient au départ (mesuré). y proximity autorise l'arrêt en cours de route, et Math.round désigne alors la section la plus proche.
Défilement lisse (CSS — scroll-behavior: smooth) smooth 280 ms par section, 540 ms pour toute la course, le point basculant à mi-chemin (140 px). Retirez-le pour un changement instantané ; c'est aussi la propriété à neutraliser sous prefers-reduced-motion.
Points (CSS — .st-snap-dot, .st-snap-dot.active) 6 px, blanc 15 % → #a855f7, halo 8 px, scale(1.3), transition .3 s Taille, couleur de repos et d'activation. 8 à 10 px se voient à distance ; l'inactif à 35 % de blanc atteint 3:1 sur les fonds sombres ; la couleur active peut reprendre la teinte d'icône de la section courante.
Clic sur les points (JS — à ajouter) absent Six lignes après l'écouteur existant : snapDots.forEach((d, i) => d.addEventListener('click', () => snapScroll.scrollTo({ top: i * 280, behavior: 'smooth' }))), avec cursor: pointer sur .st-snap-dot. Le point s'allume de lui-même à mi-course, par l'écouteur de scroll.
Indicateur « Scrollez pour explorer » (HTML — .scroll-hint-label ; CSS — pulseHint 2 s) visible en permanence, pulsation 30 → 70 % Texte libre. Pour le masquer à la dernière section, ajoutez dans l'écouteur hint.hidden = s === snapDots.length - 1 ; pour le masquer dès le premier geste, testez snapScroll.scrollTop > 0.
Fonds et icônes (CSS — .snap-section:nth-child(n), .snap-section:nth-child(n) .snap-icon) 5 dégradés à 135° + 5 teintes d'icône Dix règles couplées par position. Un seul fond pour toutes les sections (.snap-section { background: … }) donne un panneau d'application ; les dégradés vendus conviennent à un deck ou une page de présentation.

FAQ

Pas avec le code vendu : mesuré, un clic au centre du 4e point laisse scrollTop à 0 et le premier point allumé — le script (342 octets) n'écoute que le scroll du conteneur. La navigation s'ajoute en six lignes (cf. Personnaliser) et les points devraient alors devenir des <button>. Snap + Progress (fx-0597) livre ce clic d'origine (data-indexscrollTo), calcule l'index sur clientHeight et ajoute une barre de progression ; Vertical Snap (fx-0593) est en CSS pur, sans témoin. Choisissez celui-ci pour sa colonne de points en surimpression, centrée sur le bord droit, et son script minimal — en acceptant d'écrire le clic vous-même.
Parce que le script divise scrollTop par la constante 280, sans lire le DOM. Mesuré avec des sections de 360 px : 720 / 280 = 2,57, arrondi à 3, donc la section 3 allume le point 4 ; la section 5 (1 440 px) donne 5, index qui n'existe pas, et plus rien n'est allumé. Remplacez la constante par snapScroll.clientHeight (la hauteur visible du conteneur, égale à celle d'une section) et la même règle vaut pour 280 px, 360 px ou 100vh ; c'est ce que fait fx-0597.
Pas en collant le code deux fois : le script déclare const snapScroll au niveau global et vise #snap-scroll et #snap-dots, deux ids. Mesuré : le second exemplaire s'arrête sur SyntaxError: Identifier 'snapScroll' has already been declared et ne s'exécute pas. Enveloppez le script dans une fonction immédiatement invoquée et remplacez les ids par un attribut : document.querySelectorAll('[data-snap]').forEach(c => { const dots = c.parentElement.querySelectorAll('.st-snap-dot'); c.addEventListener('scroll', () => { const s = Math.round(c.scrollTop / c.clientHeight); dots.forEach((d, i) => d.classList.toggle('active', i === s)); }); }) — chaque bloc a alors ses points et sa hauteur.