ScrollGratuit

SVG Blinds Mask Transition

Huit lamelles CSS de 32,5 px, empilées en colonne flex, qui se pincent une à une vers leur ligne médiane (scaleY 1 → 0) au long des 624 px d'une zone défilante interne — une seule lamelle bouge à la fois, la légende n'apparaît que sur le dernier quart. Aucun SVG malgré le nom : dix règles CSS et 17 lignes de JavaScript qui écrivent --p.

ScrollMaskReveal
Scrollez ici ↓
Survolez ou cliquez la scène pour interagir

Cet effet est gratuit — la catégorie Scroll en contient 70 au total, dont 12 gratuits. Effect.Labs regroupe 811 effets vanilla. Découvrir la catégorie →

Exemples d'usage

Scrollez pour dévoiler ↓
Défilez pour dévoiler le lauréat

Lauréat 2026 du Prix de l'architecture : Atelier Lumen, pour la médiathèque de Sées.

① Le nom du lauréat derrière huit lamelles — page d'une cérémonie de remise de prix

QuandPage d'une cérémonie : d'abord un écran opaque en huit lamelles ; le visiteur défile et les lamelles s'ouvrent une à une sur le nom du lauréat de l'année.
PourquoiUn rideau qui s'ouvre par bandes, dans un ordre fixe, du haut vers le bas, est la métaphore du dévoilement officiel — et la légende, invisible jusqu'aux trois quarts de la course, garde le nom pour la fin. Corner Morph Overlay (fx-0779) révèle en cercle depuis un coin : une expansion, pas une levée de rideau.
RéglagesLamelles en dégradé bleu nuit avec un filet doré de 1 px, scène étirée à 100 % (max-width et bordure retirées), légende en serif de 40 px sans espacement ; l'accroche « Prix de l'architecture 2026 » est dans le haut de la photo, la ligne « pour la médiathèque » suit (var(--p) − .85) × 7 ; le nom est répété dans un texte masqué visuellement hors du bloc aria-hidden ; amorce automatique jusqu'à --p = 0,5 à l'entrée dans la fenêtre.
Maison Delacroix Biens · Estimation · L'agence Avant-première
Scrollez pour ouvrir le store ↓
Défilez pour ouvrir le store sur le bien

Avant-première : Villa Les Cyprès à Vence, 245 m², 5 chambres, piscine, vue mer, 1 250 000 €.

② Un bien en avant-première derrière un store — site d'agence immobilière

QuandPage d'un bien réservé aux membres : la façade est masquée par un store ivoire ; le visiteur qui défile ouvre les lamelles et découvre la villa, puis la fiche de prix en bas.
PourquoiLe store est celui d'une vitrine d'agence : les lamelles en dégradé ivoire ressemblent à de vraies lames, et l'ouverture du haut vers le bas livre le ciel, la façade, le bassin, puis le prix — dans l'ordre d'une visite. Corner Morph Cards (fx-0780) réorganise des cartes existantes ; ce n'est pas un masque.
RéglagesLamelles linear-gradient(#faf7f0, #ebe5d8 45%, #b9ad98) (lame claire, pied ombré), scène en flex: 1 sous une barre d'agence de 38 px, légende retirée (la fiche de prix est un bloc de la photo, révélé par les deux dernières lamelles), indice en brun sur l'ivoire ; amorce jusqu'à 0,5.
Scrollez pour tamiser ↓
Défilez pour passer du déjeuner au dîner

Le Comptoir des Saisons — déjeuner de 12 h à 14 h en salle lumineuse, dîner aux chandelles de 19 h 30 à 23 h, menu dégustation en sept services.

③ Du déjeuner au dîner, lamelle par lamelle — page d'un restaurant gastronomique

QuandPage d'un restaurant : la salle de midi, claire, occupe l'écran ; au défilement, chaque lamelle de la salle claire se referme et laisse paraître la salle du soir, aux chandelles.
PourquoiIci les lamelles ne cachent pas un fond uni : elles portent la première image. Avec background-size: 100% 800% et une position calée sur --k, les huit lamelles composent la salle de midi ; en se pinçant, chacune découvre la même salle le soir. Concentric Rings Morph (fx-0781) trace des anneaux SVG abstraits — pas de changement d'ambiance.
RéglagesLamelles : image de midi tranchée par background-position: 0 calc(var(--k) × 100% / 7) ; photo du soir en dégradés chauds avec deux bougies CSS ; étiquette « Déjeuner » au-dessus des lamelles, éteinte comme l'indice (1 − var(--p) × 3) ; légende « Dîner aux chandelles » en bas à gauche, horaires à (var(--p) − .85) × 7 ; nom du restaurant en pastille fixe au-dessus de tout.

Comment ça marche

Trois couches dans .sbm-scene (100 % de large, max-width: 430px, 260px de haut, overflow: hidden, --p: 0). Au-dessus, .sbm-scroll : un calque transparent en position: absolute; inset: 0, overflow-y: auto, z-index: 3, qui contient une piste .sbm-track de height: 340% — 884 px de contenu pour 260 px de fenêtre, soit 624 px de course mesurés. Dessous, .sbm-stage (z-index: 1, pointer-events: none, aria-hidden) empile .sbm-photo (dégradé bleu → violet → rose, légende centrée) et .sbm-slats, une colonne flex de huit span.sbm-slat en flex: 1 1 0% — 32,5 px chacun — dont le fond rgb(13, 13, 22) est celui de la scène : fermé, l'ensemble est un panneau uni. Le JavaScript (17 lignes, ES5) écoute scroll en passif sur #sbmScroll, regroupe par requestAnimationFrame et écrit --p = scrollTop / (scrollHeight − clientHeight), arrondi à quatre décimales, sur la scène. Aucun SVG nulle part : le nom vient du catalogue, le masque est fait de span empilés.

Chaque lamelle porte son rang en ligne (--k: 0 à 7) et une seule règle fait tout : transform: scaleY(clamp(0, 1 − (var(--p) × 9 − var(--k)), 1)), transform-origin: 50% 50%. La lamelle k reste à 1 tant que --p < k/9 et atteint 0 à (k + 1)/9 : 69 px de défilement par lamelle, strictement l'une après l'autre — mesuré à --p = 0,5, les lamelles 0 à 3 sont à 0, la lamelle 4 à 0,500 (16,3 px de haut sur 32,5), les lamelles 5 à 7 à 1. L'origine au centre fait que la lamelle se pince vers sa ligne médiane : la photo apparaît par les deux bords de la bande, pas par le bas comme un store qui remonte. Le multiplicateur 9 pour huit lamelles réserve le dernier neuvième (--p de 0,889 à 1, 69 px) où plus rien ne bouge sauf la légende : opacity: clamp(0, (var(--p) − .75) × 4, 1), invisible jusqu'à 468 px de défilement, pleine à 624. L'indice « Scrollez ici ↓ » suit calc(1 − var(--p) × 3) : éteint au tiers de la course (208 px).

Mesuré dans Chromium sur une page vierge : aucune erreur console, aucune couture entre lamelles à --p = 0 (colonne de pixels uniforme). Le calque de défilement couvre tout (elementFromPoint renvoie .sbm-track) : la molette est capturée sur 624 px puis la page reprend — enchaînement mesuré, la page n'a pas bougé à mi-course et a avancé de 200 px après la butée. Au clavier, la zone (tabindex="0", role="region") répond : flèche bas = 40 px (--p + 0,064), espace = 227 px. Deux limites : --p n'est recalculé qu'au prochain scroll — après un passage de 260 à 400 px de haut avec scrollTop = 312, il reste à 0,5000 alors que le vrai rapport est 0,325 (pas d'écouteur resize) ; et scrollbar-width: thin laisse une barre fine sur le bord droit de la photo là où les barres sont classiques (Windows, Linux — 0 px mesuré sur macOS). Le style="--p: 0.0000;" en ligne du HTML vendu est une trace de l'extraction, identique à l'état initial. 1,6 Ko de CSS, 0,56 Ko de JS.

Accessibilité

  • prefers-reduced-motion absent du code — mesuré sous émulation : mêmes transformations à --p = 0,5. L'effet ne bouge jamais seul (tout suit la molette), mais huit pincements successifs à chaque cran restent un mouvement. Sous @media (prefers-reduced-motion: reduce), remplacez la géométrie par un fondu piloté par la même variable : .sbm-slat { transform: none } et .sbm-slats { opacity: calc(1 − var(--p) × 1.2) } — le contenu est révélé au même rythme, sans découpe.
  • Le contenu révélé est dans un bloc aria-hidden="true" (.sbm-stage) et sous le calque de défilement (z-index: 3) : la légende — le nom d'un lauréat, une adresse — n'est ni lue par un lecteur d'écran, ni sélectionnable, ni cliquable. Écrivez le texte porteur de sens une seconde fois hors de la scène, visible ou masqué visuellement. Et sans JavaScript, --p reste à 0 : le panneau ne s'ouvre jamais.
  • Clavier : la zone est focalisable (tabindex="0", role="region", aria-label codé en français), avec un contour #a78bfa de 2 px au :focus-visible (7,1:1 sur le fond) ; flèche bas = 40 px, espace = 227 px mesurés. Contrastes : légende blanche de 20,8 px en gras sur le dégradé = 11,7:1 (bleu), 5,7:1 (violet), 4,6:1 (rose) — conforme ; indice rgb(143, 143, 176) = 6,2:1 mais en 0.6rem, soit 9,6 px, et en français sans data-en : montez-le à 0,75 rem et traduisez-le.
  • Piège au défilement : le calque transparent absorbe la molette et le doigt sur 340 % de la hauteur de la zone (624 px ici) avant de rendre la main à la page — mesuré, la page ne bouge pas tant que la zone n'est pas en butée. Gardez la scène basse (260 px vendus), ne la mettez pas en plein écran, et laissez l'indice visible : c'est le seul signal que la zone se défile de l'intérieur.

Compatibilité navigateur

Requiert clamp() et les variables CSS dans calc(), inset, transform, Element.closest() et requestAnimationFrame ; JavaScript ES5, zéro dépendance. Tout est du CSS classique piloté par un écouteur scroll : contrairement aux 45 effets « CSS natif » de la catégorie (Scroll-Driven Fade, View Timeline Scale, Clip Path Reveal…) bâtis sur animation-timeline, qu'aucune version de Firefox n'anime, celui-ci fonctionne sur Firefox.

Chrome 87+✓ Complet
Firefox 75+✓ Complet (contour de focus dès 85)
Safari 14.1+✓ Complet (contour de focus dès 15.4)
Edge 87+✓ Complet
Mobile iOS 14.5+✓ Complet (défilement au doigt)
Android Chrome✓ Complet (défilement au doigt)

Sans JavaScript, --p reste à 0 : panneau fermé, indice affiché, la zone défile dans le vide et rien n'est révélé — prévoyez une classe posée par le script (ou un bloc noscript) qui ouvre les lamelles. Sans clamp() (navigateurs antérieurs à 2020), la déclaration transform est ignorée : lamelles figées, fermées. Sans :focus-visible, la règle est écartée et le navigateur dessine son contour par défaut.

Le code

Copiez les trois blocs dans votre page. Aucune dépendance.

HTML
<div class="sbm-scene" style="--p: 0.0000;">
  <div class="sbm-scroll" id="sbmScroll" tabindex="0" role="region" aria-label="Zone de démonstration au défilement">
    <div class="sbm-track" style="height:340%">
    </div>
  </div>
  <div class="sbm-stage" aria-hidden="true">
    <div class="sbm-photo">
      <div class="sbm-caption">RÉVÉLÉ</div>
    </div>
    <div class="sbm-slats">
      <span class="sbm-slat" style="--k:0">
      </span>
      <span class="sbm-slat" style="--k:1">
      </span>
      <span class="sbm-slat" style="--k:2">
      </span>
      <span class="sbm-slat" style="--k:3">
      </span>
      <span class="sbm-slat" style="--k:4">
      </span>
      <span class="sbm-slat" style="--k:5">
      </span>
      <span class="sbm-slat" style="--k:6">
      </span>
      <span class="sbm-slat" style="--k:7">
      </span>
    </div>
  </div>
  <span class="sbm-hint">Scrollez ici ↓</span>
</div>
CSS
.sbm-scene  {
   position: relative;
   width: 100%;
   max-width: 430px;
   height: 260px;
   border-radius: 12px;
   overflow: hidden;
   border: 1px solid rgba(255, 255, 255, 0.08);
   background: rgb(13, 13, 22);
   --p: 0;
   }

.sbm-scroll  {
   position: absolute;
   inset: 0px;
   overflow-y: auto;
   z-index: 3;
   scrollbar-width: thin;
   }

.sbm-stage  {
   position: absolute;
   inset: 0px;
   z-index: 1;
   pointer-events: none;
   }

.sbm-hint  {
   position: absolute;
   right: 10px;
   bottom: 8px;
   z-index: 2;
   font-size: 0.6rem;
   letter-spacing: 0.12em;
   text-transform: uppercase;
   color: rgb(143, 143, 176);
   opacity: calc(1 - var(--p) * 3);
   pointer-events: none;
   }

.sbm-scroll:focus-visible  {
   outline: rgb(167, 139, 250) solid 2px;
   outline-offset: -2px;
   }

.sbm-track  {
   width: 100%;
   }

.sbm-photo  {
   position: absolute;
   inset: 0px;
   display: grid;
   place-items: center;
   background: radial-gradient(circle at 30% 25%, rgba(34, 255, 170, 0.25), transparent 55%), linear-gradient(160deg, rgb(20, 58, 94), rgb(124, 58, 237) 70%, rgb(219, 39, 119));
   }

.sbm-caption  {
   font-size: 1.3rem;
   font-weight: 800;
   letter-spacing: 0.3em;
   color: rgb(255, 255, 255);
   text-shadow: rgba(0, 0, 0, 0.6) 0px 2px 12px;
   opacity: clamp(0, (var(--p) - .75) * 4, 1);
   }

.sbm-slats  {
   position: absolute;
   inset: 0px;
   display: flex;
   flex-direction: column;
   }

.sbm-slat  {
   flex: 1 1 0%;
   background: rgb(13, 13, 22);
   transform: scaleY(clamp(0, 1 - (var(--p) * 9 - var(--k)), 1));
   transform-origin: 50% 50%;
   }
JavaScript (fx-0778)
(function() {
  /* fx-0778 — SVG Blinds Mask : progression de scroll → variable CSS --p (rAF, passif) */
  var sc = document.getElementById('sbmScroll');
  if (!sc) return;
  var host = sc.closest('.sbm-scene');
  var raf = null;
  function update() {
    raf = null;
    var max = sc.scrollHeight - sc.clientHeight;
    var p = max > 0 ? sc.scrollTop / max : 0;
    host.style.setProperty('--p', p.toFixed(4));
  }
  sc.addEventListener('scroll', function() {
    if (!raf) raf = requestAnimationFrame(update);
  }, { passive: true });
  update();
})();

Personnaliser

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

Option / propriétéDéfautEffet
Nombre de lamelles (HTML — span.sbm-slat avec --k) et multiplicateur (CSS — var(--p) * 9) 8 lamelles, × 9 Le multiplicateur doit valoir n + 1 pour garder une lamelle à la fois et un dernier neuvième pour la légende ; à n tout court, la dernière lamelle finit à --p = 1, en même temps que la légende. Douze lamelles de 21,7 px avec × 13 : 48 px de défilement chacune.
Course (HTML — .sbm-track height: 340%) 340 % = 624 px pour 260 px de haut Longueur de molette entre fermé et ouvert. 200% : 260 px, 29 px par lamelle, sec ; 500% : 1 040 px, lent. La page ne défile pas tant que cette course n'est pas finie.
Chevauchement (CSS — var(--p) * 9 − var(--k)) aucun : une lamelle à la fois Écrivez var(--p) * 5 − var(--k) * .5 : chaque lamelle occupe deux dixièmes de course et la suivante part à mi-chemin — deux lamelles en mouvement, l'effet « déphasé » que promet le catalogue. Forme générale --p × m − --k × d : durée 1/m, décalage d/m.
Sens du pincement (CSS — transform-origin: 50% 50%) centre : la bande s'ouvre par ses deux bords 50% 0 : la lamelle se replie contre son bord haut, la photo monte depuis le bas de chaque bande — le store qui se lève ; 50% 100% : l'inverse. Même coût, aucune autre ligne à changer.
Entrée de la légende (CSS — (var(--p) − .75) * 4) de 0,75 à 1 (468 → 624 px) Le premier terme fixe le départ, le second la vitesse (un quart de course). (var(--p) − .5) * 2 : dès la mi-course, sur toute la seconde moitié. Sous 0,75, la légende apparaît derrière des lamelles encore fermées.
Matière des lamelles (CSS — .sbm-slat background) rgb(13, 13, 22), le fond de la scène Un dégradé vertical par lamelle rend les huit bandes visibles au repos (lame de store). Pour révéler une image par une autre : background-size: 100% 800% (huit fois la hauteur) et background-position: 0 calc(var(--k) * 100% / 7), chaque lamelle montre sa tranche.
Indice (CSS — .sbm-hint, 0.6rem, calc(1 − var(--p) * 3)) 9,6 px, éteint à --p = 0,333 Texte codé en français dans le HTML. Montez à 0.75rem (12 px) ; * 6 l'éteint au sixième de la course, * 1 le garde jusqu'au bout.
Recalcul de --p (JS — update()) au scroll seulement Ajoutez window.addEventListener('resize', update) : après un changement de hauteur, la valeur reste fausse jusqu'au prochain cran (0,5000 pour un vrai 0,325 mesuré). Le toFixed(4) suffit : 624 px donnent 0,0016 par pixel.

FAQ

Le catalogue parle de lamelles « déphasées » : pourquoi une seule bouge-t-elle à la fois ?

Parce que le décalage entre lamelles (1, via − var(--k)) est égal à la durée de chaque pincement (1, la largeur du clamp) : la lamelle k commence exactement quand la lamelle k − 1 finit. Mesuré à --p = 0,5 : quatre lamelles à 0, une à 0,5, trois à 1 — jamais deux en mouvement. C'est déphasé au sens strict (chacune part à son tour), mais sans chevauchement, ce qui donne un balayage net, presque mécanique. Pour un vrai tuilage, réduisez le décalage sous la durée : var(--p) * 5 − var(--k) * .5 fait partir la suivante à mi-course de la précédente.

Les lamelles ne remontent pas, elles s'écrasent sur place — comment obtenir un store qui se lève ?

C'est transform-origin: 50% 50% : scaleY réduit la lamelle vers son propre centre, donc la photo perce par le haut et par le bas de chaque bande en même temps — à mi-course, la lamelle 4 est un trait de 16 px entre deux liserés de photo. Passez l'origine à 50% 0 : la lamelle se replie contre son bord supérieur et la photo monte depuis le bas de la bande, comme une lame de store qui se relève. 50% 100% donne l'inverse (le rideau tombe). Aucun changement de JavaScript, et le coût reste celui d'une transformation : compositeur seul, sans recalcul de mise en page.

Blinds Mask ou Corner Morph Overlay (fx-0779) pour révéler une section ?

Les deux effets partagent le même squelette — calque transparent à overflow-y: auto, --p écrit par le même script de 17 lignes — et diffèrent par le masque. fx-0779 découpe une seule couche avec clip-path: circle(calc(6% + var(--p) × 140%) at 90% 88%) : un disque né en bas à droite qui grossit sans étape, révélation continue depuis un point. fx-0778 n'utilise pas clip-path : huit span en scaleY, chacun sur son neuvième de course, révélation par bandes du haut vers le bas, à une cadence lisible. Choisissez le cercle pour une transition de page ou un changement d'ambiance sans direction ; les lamelles quand l'ordre compte (un titre en haut, un prix en bas) ou quand la métaphore du store, du rideau ou de la vitrine sert le propos.