Scroll Reveal <mv-reveal>

Revelação na rolagem: os filhos entram com fade, desfoque, deslize ou zoom de forma escalonada, uma vez ou todas as vezes; uma variante em CSS puro é controlada por uma view timeline.

CategoriaRolagem
TipoWeb Component (<mv-reveal>)
Statusestável
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

Instalação

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

Agente de IA com o servidor MCP do Marvelous UI: install_components({ slugs: ["scroll-reveal"], target_dir: "<absolute path>/src/marvelous", framework: "react" }).

Arquivos copiados (dependências incluídas): 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.

Uso

Marcação de referência, para usar como ponto de partida e personalizar com atributos, data-* e variáveis 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

NameTipoDefaultDescription
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.

Esta página foi traduzida com IA. Informar um problema de tradução