Scroll Reveal <mv-reveal>

Apparition au défilement : les enfants apparaissent en fondu, flou, glissement ou zoom, de façon échelonnée, une seule fois ou à chaque passage ; une variante en CSS pur est pilotée par une view timeline.

CatégorieDéfilement
TypeWeb Component (<mv-reveal>)
Statutstable
Keywordsreveal, blur-fade, fade-in, stagger, in-view, view-timeline, scroll-driven

When to use

  • Cards, feature items or images should fade, blur, slide or zoom in with a stagger as they enter the viewport
  • A reversible reveal tied to scroll position is wanted with pure CSS and no script
  • Content blocks on a marketing page should replay their entrance each time they come back into view

Avoid when

  • A headline should be split into letters, words or lines that animate in → use Text Reveal instead
  • Paragraph words should light up progressively in step with the scroll → use Scroll Text instead
  • Every block of a dense app screen or long form would animate; staggered entrances slow down task-focused UIs

Installation

node scripts/add.mjs scroll-reveal --out ./src/marvelous

Agent IA avec le serveur MCP Marvelous UI : install_components({ slugs: ["scroll-reveal"], target_dir: "<absolute path>/src/marvelous", framework: "react" }).

Fichiers copiés (dépendances comprises) : tokens/tokens.css, core/base.css, core/element.js, core/motion.js, core/observe.js, components/scroll-reveal/scroll-reveal.js, components/scroll-reveal/scroll-reveal.css.

Utilisation

Balisage de référence : partez de celui-ci et personnalisez-le avec les attributs, data-* et les variables CSS :

<div id="mv-reveal-demo" class="mv-scroll-area" data-orientation="vertical" data-autohide tabindex="0" aria-label="Scroll reveal demo">
  <style>
    #mv-reveal-demo { width: min(100%, 46rem); height: 400px; margin-inline: auto; border: 1px solid var(--mv-border); border-radius: var(--mv-radius-xl); background: var(--mv-bg); }
    #mv-reveal-demo .in { padding: 2rem 1.75rem 7rem; display: grid; gap: 3.5rem; }
    #mv-reveal-demo .eyebrow { margin: 0 0 .5rem; font-size: .75rem; letter-spacing: .14em; text-transform: uppercase; color: var(--mv-accent-fg); font-weight: 600; }
    #mv-reveal-demo h2 { margin: 0; font: 700 clamp(1.6rem, 3.6vw, 2.3rem)/1.1 var(--mv-font-display); letter-spacing: -.03em; }
    #mv-reveal-demo .lead { margin: .75rem 0 0; color: var(--mv-fg-muted); max-width: 34rem; line-height: 1.55; }
    #mv-reveal-demo .hint { display: inline-flex; align-items: center; gap: .4rem; margin-top: 1.25rem; font-size: .8rem; color: var(--mv-fg-subtle); }
    #mv-reveal-demo .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr)); gap: .75rem; }
    #mv-reveal-demo .tile { padding: 1.1rem 1.2rem; border: 1px solid var(--mv-border); border-radius: var(--mv-radius-lg); background: var(--mv-surface); }
    #mv-reveal-demo .tile h3 { margin: .8rem 0 .3rem; font-size: 1rem; }
    #mv-reveal-demo .tile p { margin: 0; font-size: .875rem; color: var(--mv-fg-muted); line-height: 1.5; }
    #mv-reveal-demo .ico { display: grid; place-items: center; width: 2.25rem; height: 2.25rem; border-radius: var(--mv-radius-md); background: var(--mv-accent-subtle); color: var(--mv-accent-fg); }
    #mv-reveal-demo blockquote { margin: 0; padding: 1.5rem 1.75rem; border-radius: var(--mv-radius-xl); background: linear-gradient(135deg, var(--mv-accent-subtle), transparent 70%), var(--mv-bg-subtle); border: 1px solid var(--mv-border); font-size: 1.15rem; line-height: 1.5; letter-spacing: -.01em; }
    #mv-reveal-demo figcaption { margin-top: .75rem; font-size: .85rem; color: var(--mv-fg-muted); }
    #mv-reveal-demo .stats { display: grid; grid-template-columns: repeat(4, 1fr); gap: .75rem; }
    #mv-reveal-demo .stat { padding: 1rem; border-radius: var(--mv-radius-lg); background: var(--mv-bg-muted); text-align: center; }
    #mv-reveal-demo .stat b { display: block; font-size: 1.6rem; letter-spacing: -.03em; }
    #mv-reveal-demo .stat span { font-size: .78rem; color: var(--mv-fg-muted); }
    #mv-reveal-demo .code { font: 500 .72rem var(--mv-font-mono); color: var(--mv-fg-subtle); margin: 0 0 .6rem; }
    #mv-reveal-demo ul { list-style: none; margin: 0; padding: 0; display: grid; gap: .5rem; }
    #mv-reveal-demo li { display: flex; gap: .75rem; align-items: center; padding: .8rem 1rem; border: 1px solid var(--mv-border); border-radius: var(--mv-radius-lg); background: var(--mv-surface); font-size: .9rem; }
    #mv-reveal-demo li i { flex: none; width: .5rem; height: .5rem; border-radius: 50%; background: var(--mv-accent); }
    @media (max-width: 36rem) { #mv-reveal-demo .stats { grid-template-columns: repeat(2, 1fr); } }
  </style>
  <div class="in">
    <mv-reveal effect="blur" stagger="120">
      <p class="eyebrow">What’s new · September 2026</p>
      <h2>Everything you need to ship faster.</h2>
      <p class="lead">Each block appears the moment it enters the viewport, even inside a scrolling container like this one.</p>
      <span class="hint"><svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 5v14M5 12l7 7 7-7"/></svg>Scroll down</span>
    </mv-reveal>

    <mv-reveal effect="fade-up" stagger="90" class="grid">
      <article class="tile">
        <span class="ico"><svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M13 2 3 14h9l-1 8 10-12h-9l1-8z"/></svg></span>
        <h3>Zero dependencies</h3>
        <p>Native Web Components, 3 KB on average, no framework required.</p>
      </article>
      <article class="tile">
        <span class="ico"><svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/></svg></span>
        <h3>Native animations</h3>
        <p>CSS scroll timelines, with an automatic JavaScript fallback.</p>
      </article>
      <article class="tile">
        <span class="ico"><svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg></span>
        <h3>Accessible</h3>
        <p>Respects reduced motion: content stays readable, without effects.</p>
      </article>
    </mv-reveal>

    <mv-reveal effect="slide-left">
      <figure style="margin:0">
        <blockquote>“We replaced three animation libraries with one. The site is faster and the design team is thrilled.”</blockquote>
        <figcaption>Chloe Russell, Frontend lead, Northwind Studio</figcaption>
      </figure>
    </mv-reveal>

    <div>
      <p class="code">&lt;mv-reveal effect="scale" repeat&gt;: replays every time it scrolls into view</p>
      <mv-reveal effect="scale" stagger="70" repeat class="stats">
        <div class="stat"><b>98</b><span>Lighthouse score</span></div>
        <div class="stat"><b>0</b><span>Dependencies</span></div>
        <div class="stat"><b>3 KB</b><span>Average size</span></div>
        <div class="stat"><b>AA</b><span>WCAG contrast</span></div>
      </mv-reveal>
    </div>

    <div>
      <p class="code">.mv-reveal-group data-effect="slide-right": pure CSS, follows the scroll</p>
      <ul class="mv-reveal-group" data-effect="slide-right">
        <li><i></i>Native view() timelines, no JavaScript</li>
        <li><i></i>Reversible effect: scroll back up to see it rewind</li>
        <li><i></i>Content is simply visible in older browsers</li>
      </ul>
    </div>
  </div>
</div>

API

Attributes

NameTypeDefaultDescription
effectfade-up | fade-down | fade | blur | scale | slide-left | slide-rightfade-upEntrance preset. slide-left comes in from the left, slide-right from the right.
staggernumber (ms)90Delay between two direct children.
durationnumber (ms)700Reveal duration of each child.
delaynumber (ms)0Delay before the first child.
distanceCSS length1.5remStarting offset distance.
repeatbooleanfalseHides the children again when the block leaves, so it replays every time (otherwise it plays once).
thresholdnumber 0-10.2Visible fraction of the block that triggers the reveal.
marginrootMargin0px 0px -8% 0pxMargin of the trigger area.
scrollerselectorScrolling ancestor used as the root. Defaults to the viewport, clipped by every scrolling ancestor, so it works inside any container.
data-effectsame as effectPreset for the .mv-reveal / .mv-reveal-group CSS classes.

Methods

NameDescription
reveal()Shows all children immediately.

Events

NameDescription
mv-revealBlock revealed or hidden. detail: { visible }.

CSS classes

NameDescription
mv-revealPure CSS: the element appears in step with the scroll (animation-timeline: view()), reversible. Without support: simply visible.
mv-reveal-groupPure CSS: each child appears, staggered via sibling-index() when available.

CSS variables

NameDefaultDescription
--mv-reveal-distance1.5remOffset distance.
--mv-reveal-blur12pxStarting blur of the blur effect.
--mv-reveal-easingvar(--mv-ease-out)Easing of <mv-reveal>.
--mv-reveal-stagger / -duration / -delayCSS equivalents of the attributes.
--mv-reveal-iChild index, written by the JS (read-only).

Accessibility

Content stays in the DOM and readable by screen readers at all times; only its visual rendering is animated. Reduced motion: everything is shown immediately, without offset or blur. Before the JS runs (SSR), content is visible. Print: everything is visible.

Cette page a été traduite par IA. Signaler un problème de traduction