Parallax <mv-parallax>
Profundidade em camadas na rolagem sem configuração: marque qualquer descendente com data-speed e ele se desloca no próprio ritmo enquanto a seção atravessa o contêiner de rolagem, movido por uma view timeline nativa fora da thread principal, com um fallback leve em JS que só atua no que está visível.
| Categoria | Rolagem |
|---|---|
| Tipo | Web Component (<mv-parallax>) |
| Status | estável |
| Keywords | safe-rewrite, parallax, layers, hero, depth, gallery, view-timeline, scroll-driven |
When to use
- A hero or gallery section should gain depth by moving background and foreground layers at different speeds
- Decorative shapes, words or images should drift horizontally or vertically as a section crosses the viewport
- Parallax is needed inside a scrollable panel, not only on the page itself
Avoid when
- Elements should simply fade or slide in once when they appear → use Scroll Reveal instead
- The pointer, not the scroll, should drive layer depth on a card → use Tilt Card instead
- Many large layered sections stack up on one page; strong motion there can cause discomfort
Instalação
node scripts/add.mjs parallax --out ./src/marvelousAgente de IA com o servidor MCP do Marvelous UI: install_components({ slugs: ["parallax"], 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, components/parallax/parallax.js, components/parallax/parallax.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-parallax-demo" class="mv-scroll-area" data-orientation="vertical" tabindex="0" aria-label="Parallax demo, scroll to explore">
<style>
#mv-parallax-demo { width: min(100%, 52rem); height: 440px; margin-inline: auto; border: 1px solid var(--mv-border); border-radius: var(--mv-radius-xl); background: var(--mv-bg); }
#mv-parallax-demo .copy { padding: 2.5rem 1.5rem; text-align: center; }
#mv-parallax-demo .copy p { margin: .5rem auto 0; max-width: 30rem; color: var(--mv-fg-muted); font-size: .92rem; line-height: 1.55; }
#mv-parallax-demo .kicker { margin: 0; font-size: .72rem; font-weight: 600; letter-spacing: .16em; text-transform: uppercase; color: var(--mv-accent-fg); }
#mv-parallax-demo h2 { margin: .4rem 0 0; font: 700 1.6rem/1.15 var(--mv-font-display); letter-spacing: -.03em; }
/* 1 · Layered scene */
#mv-parallax-demo .scene { height: 440px; margin: 0 1rem; border-radius: var(--mv-radius-xl); color: oklch(.98 .01 80); background: linear-gradient(180deg, oklch(.2 .05 275) 0%, oklch(.34 .09 300) 45%, oklch(.66 .14 40) 78%, oklch(.8 .12 65) 100%); }
#mv-parallax-demo .fill { position: absolute; inset-inline: 0; pointer-events: none; }
#mv-parallax-demo .fill svg { display: block; width: 100%; height: 100%; }
#mv-parallax-demo .sky { top: 0; height: 70%; background-image: radial-gradient(2px 2px at 8% 30%, #fffc, transparent), radial-gradient(1.5px 1.5px at 19% 62%, #fff9, transparent), radial-gradient(2px 2px at 33% 18%, #fffb, transparent), radial-gradient(1.5px 1.5px at 47% 44%, #fff8, transparent), radial-gradient(2px 2px at 61% 26%, #fffa, transparent), radial-gradient(1.5px 1.5px at 74% 58%, #fff8, transparent), radial-gradient(2px 2px at 86% 16%, #fffc, transparent), radial-gradient(1.5px 1.5px at 94% 40%, #fff9, transparent); }
#mv-parallax-demo .moon { position: absolute; top: 16%; left: 16%; width: 4.5rem; aspect-ratio: 1; border-radius: 50%; background: radial-gradient(circle at 36% 34%, oklch(.99 .02 90), oklch(.9 .05 85) 60%, oklch(.82 .06 80)); box-shadow: 0 0 60px 12px oklch(.95 .06 85 / .35); }
#mv-parallax-demo .heading { position: absolute; inset: 22% 1rem auto; text-align: center; text-shadow: 0 8px 32px oklch(.15 .05 290 / .5); }
#mv-parallax-demo .heading p { margin: 0; font-size: .72rem; letter-spacing: .32em; text-transform: uppercase; opacity: .85; }
#mv-parallax-demo .heading h3 { margin: .35rem 0 0; font: 800 clamp(2.2rem, 7vw, 4rem)/.95 var(--mv-font-display); letter-spacing: -.045em; }
#mv-parallax-demo .far { top: 52%; bottom: -20%; color: oklch(.5 .09 330); }
#mv-parallax-demo .near { top: 64%; bottom: -20%; color: oklch(.36 .08 310); }
#mv-parallax-demo .ground { top: 80%; bottom: -1px; color: oklch(.2 .04 290); }
#mv-parallax-demo .chip { position: absolute; right: 1.25rem; bottom: 1.25rem; padding: .45rem .85rem; border-radius: var(--mv-radius-full); background: oklch(1 0 0 / .14); border: 1px solid oklch(1 0 0 / .25); backdrop-filter: blur(8px); font-size: .75rem; font-weight: 500; }
/* 2 · Horizontal drift */
#mv-parallax-demo .drift { display: grid; gap: .25rem; padding: 2.5rem 0; }
#mv-parallax-demo .drift p { margin: 0; white-space: nowrap; text-align: center; font: 800 clamp(2rem, 6vw, 3.4rem)/1.05 var(--mv-font-display); letter-spacing: -.04em; }
#mv-parallax-demo .drift p + p { color: transparent; -webkit-text-stroke: 1px var(--mv-fg-muted); }
/* 3 · Staggered columns */
#mv-parallax-demo .grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: .75rem; padding: 2rem 1rem 3rem; }
#mv-parallax-demo .stack { display: grid; gap: .75rem; align-content: start; }
#mv-parallax-demo .stack:nth-child(2) { margin-top: 2.5rem; }
#mv-parallax-demo figure { margin: 0; height: 140px; display: flex; align-items: flex-end; padding: .75rem; border-radius: var(--mv-radius-lg); color: white; font-size: .8rem; font-weight: 600; text-shadow: 0 1px 8px #0007; }
</style>
<div class="copy">
<p class="kicker">Field notes</p>
<h2>A night in the Atacama</h2>
<p>Scroll the panel: every layer of the scene moves at its own pace.</p>
</div>
<mv-parallax class="scene">
<div class="fill sky" data-speed="-0.4" aria-hidden="true"></div>
<div class="moon" data-speed="-0.34" aria-hidden="true"></div>
<div class="heading" data-speed="-0.26">
<p>San Pedro de Atacama</p>
<h3>Valley of the Moon</h3>
</div>
<div class="fill far" data-speed="-0.16" aria-hidden="true"><svg viewBox="0 0 1200 300" preserveAspectRatio="none" fill="currentColor"><path d="M0 300V120C120 70 220 60 330 95S560 40 690 70 930 30 1040 80 1160 90 1200 70V300Z"/></svg></div>
<div class="fill near" data-speed="-0.07" aria-hidden="true"><svg viewBox="0 0 1200 300" preserveAspectRatio="none" fill="currentColor"><path d="M0 300V110C150 60 260 120 400 90S640 30 780 85 1010 120 1200 60V300Z"/></svg></div>
<div class="fill ground" aria-hidden="true"><svg viewBox="0 0 1200 120" preserveAspectRatio="none" fill="currentColor"><path d="M0 120V50C180 20 330 60 520 40S860 10 1020 38 1150 44 1200 30V120Z"/></svg></div>
<span class="chip" data-speed="0.12">7,900 ft · Clear skies</span>
</mv-parallax>
<mv-parallax class="drift">
<p data-speed-x="0.35">Salt flats · Geysers · Lagoons</p>
<p data-speed-x="-0.35" aria-hidden="true">Salt flats · Geysers · Lagoons</p>
</mv-parallax>
<mv-parallax class="grid" style="--mv-parallax-overflow: visible">
<div class="stack" data-speed="0.1">
<figure style="background: linear-gradient(160deg, oklch(.8 .1 60), oklch(.5 .14 35))">El Tatio Geysers</figure>
<figure style="background: linear-gradient(160deg, oklch(.82 .08 210), oklch(.5 .1 240))">Laguna Cejar</figure>
<figure style="background: linear-gradient(160deg, oklch(.78 .1 20), oklch(.45 .13 10))">Rainbow Valley</figure>
</div>
<div class="stack" data-speed="-0.1">
<figure style="background: linear-gradient(160deg, oklch(.86 .04 250), oklch(.52 .08 270))">Salar de Atacama</figure>
<figure style="background: linear-gradient(160deg, oklch(.76 .12 300), oklch(.42 .14 285))">Stargazing at 2 a.m.</figure>
<figure style="background: linear-gradient(160deg, oklch(.84 .08 95), oklch(.55 .11 70))">Death Valley Dunes</figure>
</div>
<div class="stack" data-speed="0.1">
<figure style="background: linear-gradient(160deg, oklch(.8 .09 180), oklch(.46 .1 200))">Miscanti Lagoon</figure>
<figure style="background: linear-gradient(160deg, oklch(.8 .1 45), oklch(.5 .13 25))">Pukará de Quitor</figure>
<figure style="background: linear-gradient(160deg, oklch(.78 .08 140), oklch(.45 .09 160))">Jere Valley</figure>
</div>
</mv-parallax>
<div class="copy">
<p class="kicker">End of the road</p>
<p>With reduced motion turned on, every layer stays exactly where it was placed.</p>
</div>
</div>API
Attributes
| Name | Tipo | Default | Description |
|---|---|---|---|
data-speed | number | 0 | On a descendant: relative vertical speed. Negative = slower than the scroll (background, e.g. -0.3), positive = faster (foreground). Every layer sits at its authored position when the section is centered. |
data-speed-x | number | 0 | On a descendant: horizontal drift proportional to the scroll. |
scroller | selector | window | Reference scroll container (forces the JS path). Default: the nearest scroll container, or the page. | |
fallback | boolean | Forces the JavaScript fallback (tests, browsers without animation-timeline). |
Content structure
| Name | Description |
|---|---|
[data-speed] / [data-speed-x] | Any non-inline descendant (block, image, SVG). A layer runs its own translate animation: wrap it if it needs another animation or translate. Make background layers taller than the section: the maximum offset is |speed| × (section height + scrollport height) / 2. |
CSS variables
| Name | Default | Description |
|---|---|---|
--mv-parallax-intensity | 1 | Global speed multiplier. |
--mv-parallax-overflow | clip | Section overflow (visible to let layers spill out). |
Accessibility
Purely visual: DOM order, focus order and content never change. Mark decorative layers aria-hidden. Reduced motion (OS setting or data-motion="reduce"): no animation at all, layers stay at their authored position. No scroll hijacking and no wheel or touch listeners: the native path runs on a view timeline; the fallback uses one passive scroll listener, one frame at a time, only while the section is near the viewport.