Scroll✨ Premium

Zoom on Scroll

Un carré de 60 px collé (sticky) dans un bloc de 280 px, dont l'échelle suit le défilement — scale = 1 + 4 × scrollTop ÷ 620 — jusqu'à 300 px, pendant que son rayon passe de 12 à 2 px (10 px à l'écran une fois grossi) et son opacité de 1 à .8. À 5×, le carré dépasse le bloc de 40 px par le haut.

JavaScriptCSS TransformScroll

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

Étiquette de traçabilité alimentaire en fiche produit bio — le QR grandit jusqu'à être scannable — Zoom on Scroll exemple 1

① Étiquette de traçabilité alimentaire en fiche produit bio — le QR grandit jusqu'à être scannable

QuandFiche produit d'un producteur en vente directe : la section « Origine » montre un QR de traçabilité en petite vignette arrondie ; en défilant vers elle, le code remplit la colonne et ses coins s'affûtent, le client le scanne depuis son téléphone sans bouton ni fenêtre.
PourquoiLa progression 1 → 5× indexée sur scrollTop laisse le client choisir lui-même le moment où le QR atteint une taille scannable — pas de bouton « agrandir », pas de modale, le geste de défilement suffit et reste dans la lecture de la fiche. Le carré blanc à coins arrondis se lit comme une vignette ; à 5×, c'est une étiquette.
RéglagesCode vendu intact (mêmes ids, même script) : bloc de 326 px, top: 128px pour centrer le carré (le top: 80px d'origine rognerait 40 px), fond blanc et 4 px de marge autour d'un QR en SVG de 25 modules (2,1 px par module au repos, 10,4 px à 5×) ; course 574 px, 143 px par unité d'échelle. L'opacité finale de .8 vient du code : le QR reste lisible, le blanc vire au gris clair.
Infographie-clé dans un article d'investigation en ligne — le graphique s'ouvre à la lecture fine — Zoom on Scroll exemple 2

② Infographie-clé dans un article d'investigation en ligne — le graphique s'ouvre à la lecture fine

QuandArticle de presse numérique : une courbe de synthèse (dette publique en pourcentage du PIB) est posée en vignette compacte dans le fil du texte ; en défilant jusqu'à elle, elle grossit jusqu'à remplir la fenêtre et laisse lire axes et annotations.
PourquoiL'agrandissement suit le défilement, donc l'attention : le lecteur qui ralentit sur le graphique le voit grandir, celui qui passe le laisse en vignette. Un clic ouvrant une modale casserait le rythme de lecture ; ici le zoom est proportionnel à l'attention, et le retour arrière le referme.
RéglagesCible de 96 × 60 px (480 × 300 à 5×) dans un bloc de 320 px, top: 126px ; graphique en SVG, dont les textes de 3,4 px deviennent 17 px ; opacity figée à 1 — le 1 − .2t du code grisait les axes ; instance propre (#inv-scroll) avec le même calcul : le code vendu ne pilote qu'un id.
Équation de réaction dans un cours de chimie organique — la formule passe de référence à pièce centrale — Zoom on Scroll exemple 3

③ Équation de réaction dans un cours de chimie organique — la formule passe de référence à pièce centrale

QuandCours en ligne universitaire : l'équation de l'estérification de Fischer est posée en carte compacte au centre d'une section ; l'étudiant défile, la carte grossit jusqu'à occuper toute la zone de lecture et ses coins s'effacent.
PourquoiLa perte du rayon en même temps que le zoom encode un changement de statut — carte parmi d'autres, puis dalle qui occupe le cadre — sans annotation ni infobulle. Aucun autre effet de la catégorie ne couple une forme géométrique qui change à un zoom piloté par la position de défilement.
RéglagesCible de 78 × 34 px (390 × 170 à 5×) dans une zone de 400 × 320 px, top: 139px ; rayon poussé à zéro avec 12 − 12t (le code s'arrête à 2 px, 10 px rendus) ; formule en SVG, indices en caractères Unicode ; instance propre (#chem-scroll).

Comment ça marche

Le HTML vendu tient en quatre éléments : le cadre .demo-preview (flex centré, min-height: 300px, position: relative, fond #0a0a0f), le bloc défilant .scroll-container#zoom-scroll (100% × 280px, overflow: hidden auto, scroll-behavior: smooth, barre de 4 px stylée par ::-webkit-scrollbar), le plateau .zoom-inner (flex, justify-content: center, align-items: flex-start, 900 px de haut, padding-top: 100px) et la cible .zoom-target#zoom-target : 60 × 60 px, dégradé 135° #6366f1 → #a855f7 → #ec4899, border-radius: 12px, position: sticky; top: 80px, le mot ZOOM en 11,2 px graisse 800, et transition: transform .1s linear, border-radius .3s. Sous le bloc, l'étiquette .scroll-hint-label (absolue, à 8 px du bas du cadre, 10,4 px, blanc à 30 %) pulse entre .3 et .7 d'opacité toutes les 2 s ; son texte « Scrollez pour explorer » est écrit en dur dans le HTML. La course vaut 900 − 280 = 620 px. Aucun style en ligne, aucune variable CSS du site, aucune erreur console.

Le script (10 lignes) pose un seul écouteur scroll sur le bloc. À chaque événement : t = scrollTop ÷ (scrollHeight − clientHeight), soit scrollTop ÷ 620, puis trois écritures dans styletransform: scale(1 + 4t), border-radius: max(12 − 10t, 0), opacity: 1 − .2t. Une variable, trois sorties, 155 px de course par unité d'échelle. Mesuré par crans de 62 px : 1,4 / 11 px / .98 au premier cran, 3 / 7 px / .9 à mi-course, 5 / 2 px / .8 en bas. Le rayon ne descend jamais à zéro : 12 − 10t vaut 2 px à t = 1 (le Math.max(…, 0) ne sert jamais), et comme transform agrandit aussi les coins, l'angle rendu mesure 10 px à 5× — la « dalle » garde un arrondi de 3 % du côté, contre 20 % au repos. Les deux transitions n'ont pas la même durée : après un saut de 245 px (PageDown), l'échelle atteint sa valeur en 104 ms, le rayon met 300 ms — au moment où la taille se fige, le coin affiche encore 6,2 px (31 px rendus) et finit de se resserrer 200 ms plus tard.

La cible part à 100 px du haut (le padding-top), glisse de 20 px, puis sticky; top: 80px la fixe : son centre reste à 110 px du haut du bloc pendant les 600 px restants, et le transform grossit autour de ce centre (transform-origin 30 px 30 px). À 5×, le carré de 300 px s'étend de −40 à 260 px : 40 px sont rognés par le haut (13 % du carré) par overflow: hidden, 20 px restent libres en bas — mesuré identique à 320, 800 et 1 440 px de large ; la largeur ne change que les marges latérales (2 px de chaque côté dans un bloc de 304 px). Le scroll-behavior: smooth n'agit pas sur la molette dans Chromium (un cran de 100 px = 2 événements en 114 ms) mais anime les positions posées par script : scrollTop = 620 se relit 0 immédiatement, arrive à 408 ms, et l'échelle finit à 508 ms. L'écouteur vit sur le bloc, rien de global à retirer ; en revanche const zoomScroll au niveau global et getElementById limitent le code à une instance par page — collé deux fois : Identifier 'zoomScroll' has already been declared, seconde copie inerte (mesuré : transform: none après 620 px de défilement).

Accessibilité

  • prefers-reduced-motion absent du code : mesuré sous émulation, l'échelle atteint 5 en bas de course, l'étiquette pulse toujours et les transitions de 100 et 300 ms restent. Le grossissement est piloté par le lecteur (rien ne bouge sans lui, hormis l'étiquette), ce qui le range parmi les animations déclenchées par l'interaction (WCAG 2.3.3) — ajoutez @media (prefers-reduced-motion: reduce) { .zoom-target { transition: none } .scroll-hint-label { animation: none } .scroll-container { scroll-behavior: auto } }, et si le contenu du carré est lisible au repos, plafonnez l'échelle (1 + 2t) derrière matchMedia.
  • Clavier : mesuré dans Chromium, Tab donne le focus au bloc sans tabindex (anneau outline: auto 1px), flèche bas avance de 40 px (+0,26 d'échelle), PageDown ou Espace de 245 px (+1,58), Fin va au bout (5×). Firefox et Safari ne focalisent pas un bloc défilant sans tabindex="0" sur #zoom-scroll : ajoutez-le avec un :focus-visible visible. Molette et défilement tactile imbriqué fonctionnent ; sur écran tactile, le geste doit commencer dans le bloc.
  • Contraste et lisibilité : le mot ZOOM (blanc, graisse 800) mesure 4,5:1 sur le bleu, 4,0:1 sur le violet et 3,5:1 sur le rose du dégradé — sous 4,5:1 sur les deux tiers du carré, à 11,2 px. À 5×, il fait 56 px (seuil 3:1) mais l'opacité tombe à .8 : texte et fond se fondent vers #0a0a0f, 3,9:1 / 3,6:1 / 3,2:1 — tenu de peu sur le rose. Si le carré porte un vrai contenu (QR, graphique, formule), gardez opacity: 1 et corrigez le top : au maximum, les 40 px du haut sont hors cadre, donc illisibles. L'étiquette pulse entre 2,6:1 et 9,8:1 : consigne peu lisible la moitié du temps.
  • Lecteurs d'écran et sans JavaScript : la cible est un div dont le texte est lu tel quel ; le changement d'échelle n'est pas annoncé (rien à annoncer s'il est décoratif : posez aria-hidden="true"). Si le carré contient une information, elle doit exister à taille lisible sans défiler — un lecteur d'écran ne « zoome » pas par le défilement. L'étiquette « Scrollez pour explorer » est lue en français quelle que soit la langue de la page. Sans script, le bloc défile, le carré glisse de 100 à 80 px puis reste collé à 60 px, rayon 12 : rien ne grossit, seule l'étiquette pulse.

Compatibilité navigateur

Script ES2015 (const, fonction fléchée, gabarit `scale(${e})`), position: sticky, transition et un dégradé. Une seule propriété récente : la forme à deux valeurs overflow: hidden auto — un navigateur qui ne la comprend pas ignore la déclaration, le bloc s'étire et rien ne défile. Pas d'animation-timeline ni de scroll-timeline : l'échelle vient d'un écouteur scroll classique qui écrit un transform, l'effet fonctionne donc sur Firefox, contrairement aux voisins pilotés au scroll en CSS natif (fx-0590, fx-0570). Zéro dépendance.

Chrome 68+✓ Complet
Firefox 61+✓ Complet (barre de défilement native, ::-webkit-scrollbar ignoré)
Safari 13+✓ Complet (défilement doux ignoré avant 15.4)
Edge 79+✓ Complet
Mobile iOS 13+✓ Complet (défilement tactile du bloc)
Android Chrome✓ Complet

Sans JavaScript, le bloc défile, le carré glisse de 20 px puis reste collé à 60 px, rayon 12, opacité 1 : rien ne grossit, seule l'étiquette pulse. Sans overflow: hidden auto (Chrome < 68, Firefox < 61, Safari < 12.1), le bloc mesure 900 px et rien ne défile. Sans position: sticky, le carré remonte avec le contenu tout en grossissant et sort par le haut.

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="zoom-scroll">
    <div class="zoom-inner">
      <div class="zoom-target" id="zoom-target">…</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
Facteur d'échelle (JS — e = 1 + 4 * t) 1 → 5× (60 → 300 px) Taille finale = côté × facteur. Dans un bloc de 280 px, 5× ne tient pas (300 px) : 4,6× est le maximum centrable (1 + 3.6 * t). 1 + 1 * t donne un discret 2×, lisible comme une mise en avant.
Rayon (JS — r = 12 - 10 * t, Math.max(r, 0)) 12 → 2 px (10 px rendus à 5×) Le transform agrandit aussi les coins : rayon rendu = rayon × échelle. Pour de vrais angles droits, 12 − 12 * t ; pour un arrondi constant à l'écran, 12 / e. Le Math.max ne sert que si la pente dépasse 12.
Opacité (JS — 1 - .2 * t) 1 → .8 La dalle devient translucide à 20 % en bout de course et son texte blanc vire au gris (#cecece sur fond sombre). Passez à 1 pour un contenu à lire, ou inversez (.6 + .4 * t) pour faire émerger le carré.
Course (CSS — .zoom-inner { height: 900px }, .scroll-container { height: 280px }) 620 px (155 px par unité) La course vaut hauteur du plateau − hauteur du bloc, relue par le script (scrollHeight − clientHeight) : rien à changer en JS. 1 500 px de plateau = 1 220 px de course, 305 px par unité, zoom plus lent ; le plateau est aussi le bloc conteneur du sticky, la cible y reste collée jusqu'au bout.
Point de fixation (CSS — .zoom-target { top: 80px }) 80 px (centre à 110 px, 40 px rognés à 5×) Le centre du carré se fixe à top + 30 px. Pour le centrer dans le bloc : top = hauteur ÷ 2 − 30, soit 110 px pour 280 px. Le carré final doit tenir dans le bloc : 300 px exigent un bloc d'au moins 300 px (326 px et top: 128px dans la première scène).
Taille et contenu (CSS — width/height: 60px, HTML — le texte ZOOM) 60 × 60 px, mot ZOOM 11,2 px Tout contenu est agrandi avec la boîte : un texte de 11,2 px fait 56 px à 5×, un <img> de 60 px est étiré à 300 px (flou) — fournissez une source de 300 px affichée en 60 px, ou du SVG (QR, graphique, formule). Le carré n'est pas obligatoire : 96 × 60 px donnent 480 × 300.
Réactivité (CSS — transition: transform .1s linear, border-radius .3s) 100 ms / 300 ms Retard entre le défilement et la taille : à 0 le carré colle au geste, à .3s il flotte. Les 300 ms du rayon le font traîner 200 ms derrière la taille (31 px rendus au moment où l'échelle se fige, 10 px ensuite) : alignez les deux sur .1s.
Défilement doux et étiquette (CSS — scroll-behavior: smooth, .scroll-hint-label) smooth / « Scrollez pour explorer » pulsant 2 s Le smooth anime les positions posées par script (scrollTop = 620 : relu 0, atteint en 408 ms) — passez à auto si un bouton ou une ancre pilote le bloc. L'étiquette est un reste du catalogue : française en dur, absolue par rapport à .demo-preview (sans ce cadre elle tombe au bas de la page), sur deux lignes sous 341 px de large.

FAQ

Parce que la cible est collée à top: 80px : son centre se fixe à 110 px du haut d'un bloc de 280 px, et le transform grossit autour de ce centre. À 5×, 300 px s'étendent de −40 à 260 px : les 40 px du haut sortent du bloc, que overflow: hidden auto coupe, alors que 20 px restent libres en bas. Mesuré identique à 320, 800 et 1 440 px de large — la largeur n'y change rien. Deux réglages : centrer avec top: 110px et plafonner à 4,6× (1 + 3.6 * t, 276 px), ou garder 5× dans un bloc d'au moins 300 px (height: 326px, top: 128px). Les trois scènes de cette page centrent la cible et dimensionnent le bloc pour que la dalle finale tienne entière.
Non. Le script écrit 12 − 10t, donc 2 px à t = 1 — le Math.max(r, 0) ne sert jamais, la valeur ne descend pas sous 2. Et comme transform: scale() agrandit la boîte avec ses coins, le rayon rendu vaut rayon × échelle : 12 px au repos, 10 px à 5×. En proportion du côté, l'arrondi passe de 20 % à 3,3 % — c'est ce passage qui donne l'impression de dalle, pas un angle droit. Pour de vrais angles droits, écrivez 12 − 12t ; pour un arrondi constant à l'écran, 12 / e. Notez aussi que la transition du rayon dure 300 ms contre 100 ms pour l'échelle : après un PageDown, le coin est encore à 6,2 px (31 px rendus) quand la taille est déjà figée, et finit de se resserrer 200 ms plus tard.
Le rendu de départ se ressemble — carré en dégradé, mot ZOOM, coins qui se resserrent — mais fx-0590 est écrit en CSS natif : animation-timeline: scroll() anime scale(1) → scale(2.2) et border-radius: 20px → 4px sur une boîte de 120 px posée au milieu d'un plateau de 800 px, sans sticky malgré son nom et avec un champ JavaScript vide. Chrome 115+, Edge 115+, Safari 26 ; rien sur Firefox, qui n'active pas les animations pilotées au scroll. fx-0580 fait le calcul en JavaScript : un écouteur scroll, une variable t, un transform écrit à chaque événement — ça tourne sur Firefox 61+, la cible reste collée à 110 px pendant que le plateau défile derrière elle, le facteur monte à 5× au lieu de 2,2 et l'opacité baisse à .8. Surtout, t est un nombre que le reste de la page peut lire : afficher un pourcentage, débloquer un bouton, changer un texte — hors de portée d'une animation CSS.