Reveal Mask <mv-reveal-mask>

Círculo suave que sigue a un puntero preciso y descubre una segunda capa, con una estela opcional que se desvanece; los toques, el foco del teclado y revealed lo abren como un iris, y con movimiento reducido aparece con un fundido.

CategoríaCursores
TipoWeb Component (<mv-reveal-mask>)
Estadoestable
Keywordsmask, reveal, spotlight, flashlight, hover, cursor, trail, before-after

When to use

  • A hover circle should uncover an alternate layer: a punchline, a second image or a day version of a scene
  • A flashlight or paint-trail reveal suits a playful hero or an easter egg that still works on touch and keyboard

Avoid when

  • Two images must be compared precisely with a keyboard-accessible divider → use Image Compare instead
  • Content should be magnified under the pointer rather than swapped → use Lens instead
  • Essential information lives only in the hidden layer; meaning must stay in the visible layer

Instalación

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

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

Archivos copiados (dependencias incluidas): tokens/tokens.css, core/base.css, core/element.js, core/focus.js, core/motion.js, components/reveal-mask/reveal-mask.js, components/reveal-mask/reveal-mask.css.

Uso

Marcado de referencia: parte de él y personalízalo con atributos, data-* y variables CSS:

<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(300px,1fr));gap:1rem;width:100%">
  <div style="display:grid;gap:.6rem;align-content:start">
    <mv-reveal-mask id="mv-reveal-demo" size="120" softness="3" trail="8" aria-label="Manifesto" style="border-radius:var(--mv-radius-xl);border:1px solid var(--mv-border);background:var(--mv-surface);min-height:260px">
      <div style="display:grid;align-content:center;gap:.75rem;min-height:260px;padding:2rem">
        <p style="margin:0;font:600 .72rem/1 var(--mv-font-sans);letter-spacing:.18em;text-transform:uppercase;color:var(--mv-fg-subtle)">Manifesto</p>
        <p style="margin:0;font:800 clamp(1.6rem,3.2vw,2.4rem)/1.08 var(--mv-font-sans);letter-spacing:-.035em;color:var(--mv-fg-muted)">We ship sites that are <span style="color:var(--mv-fg)">fast</span>, clean and tidy.</p>
      </div>
      <div data-reveal style="display:grid;align-content:center;gap:.75rem;padding:2rem;background:var(--mv-accent);color:var(--mv-fg-on-accent)">
        <p style="margin:0;font:600 .72rem/1 var(--mv-font-sans);letter-spacing:.18em;text-transform:uppercase;opacity:.8">Between the lines</p>
        <p style="margin:0;font:800 clamp(1.6rem,3.2vw,2.4rem)/1.08 var(--mv-font-sans);letter-spacing:-.035em">And we hide a surprise in every pixel.</p>
      </div>
    </mv-reveal-mask>
    <p style="margin:0;font-size:.8rem;color:var(--mv-fg-muted)">Hover to paint with a fading trail. Tap, or focus with Tab, to open it all.</p>
  </div>

  <div style="display:grid;gap:.6rem;align-content:start">
    <mv-reveal-mask id="mv-reveal-demo-night" size="110" softness="44" aria-label="The Alps, by night and by day" style="border-radius:var(--mv-radius-xl);min-height:260px;background:linear-gradient(180deg,oklch(0.18 0.04 270),oklch(0.26 0.06 290))">
      <div style="position:relative;min-height:260px;display:grid;align-content:end;padding:1.25rem;color:oklch(0.92 0.02 270)">
        <div aria-hidden="true" style="position:absolute;inset:0;background:radial-gradient(1.5px 1.5px at 20% 25%,white,transparent),radial-gradient(1.5px 1.5px at 70% 15%,white,transparent),radial-gradient(1px 1px at 45% 40%,white,transparent),radial-gradient(1.5px 1.5px at 85% 45%,white,transparent),radial-gradient(1px 1px at 30% 60%,white,transparent);opacity:.8"></div>
        <div aria-hidden="true" style="position:absolute;left:0;right:0;bottom:0;height:45%;background:oklch(0.14 0.03 280);clip-path:polygon(0 60%,12% 35%,20% 55%,34% 20%,46% 50%,58% 30%,70% 55%,82% 25%,100% 50%,100% 100%,0 100%)"></div>
        <strong style="position:relative;font-size:1rem">The Alps by night</strong>
        <span style="position:relative;font-size:.8rem;opacity:.75">Find the daylight with the flashlight.</span>
      </div>
      <div data-reveal style="display:grid;align-content:end;padding:1.25rem;color:oklch(0.22 0.05 250);background:linear-gradient(180deg,oklch(0.85 0.09 220),oklch(0.93 0.06 90))">
        <div aria-hidden="true" style="position:absolute;right:18%;top:18%;width:56px;height:56px;border-radius:50%;background:oklch(0.92 0.14 95);box-shadow:0 0 40px oklch(0.92 0.14 95)"></div>
        <div aria-hidden="true" style="position:absolute;left:0;right:0;bottom:0;height:45%;background:oklch(0.62 0.12 150);clip-path:polygon(0 60%,12% 35%,20% 55%,34% 20%,46% 50%,58% 30%,70% 55%,82% 25%,100% 50%,100% 100%,0 100%)"></div>
        <strong style="position:relative;font-size:1rem">The Alps by day</strong>
        <span style="position:relative;font-size:.8rem;opacity:.8">Bright sun over the ridges.</span>
      </div>
    </mv-reveal-mask>
    <button class="mv-button" data-variant="outline" id="mv-reveal-demo-toggle" aria-pressed="false" style="justify-self:start">Reveal all</button>
  </div>
</div>
<script type="module">
  const btn = document.getElementById("mv-reveal-demo-toggle");
  const mask = document.getElementById("mv-reveal-demo-night");
  btn?.addEventListener("click", () => {
    const on = !mask.hasAttribute("revealed");
    mask.toggleAttribute("revealed", on);
    btn.setAttribute("aria-pressed", String(on));
    btn.textContent = on ? "Back to night" : "Reveal all";
  });
</script>

API

Attributes

NameTipoDefaultDescription
sizenumber160Circle radius on hover (px).
idlenumber0Radius when not hovered (px): a small permanent peephole if > 0 (pointer mode only).
softnessnumber2Soft edge width (px; large = flashlight effect).
smoothing0.02..0.90.18Fraction of the distance covered each frame (smaller = more lag).
trail0..120Number of fading circles left behind the pointer (a soft wake that fades out in half a second).
revealedbooleanFully opens the hidden layer (iris from the current center).
disabledbooleanDisables and hides the hidden layer.
data-active(state)Set by the component while a mouse or hovering pen is over it.
data-open(state)Set by the component after a tap opened the layer; the next tap closes it.
data-still(state)Set when the circle does not track (reduced motion, or no fine hovering pointer): the layer fades in instead.

Events

NameDescription
mv-toggleA tap opened or closed the layer. detail: { open }.

Content structure

NameDescription
(first content)Visible layer.
[data-reveal]Revealed layer, laid on top (inset: 0). Stays in the accessibility tree; add aria-hidden="true" yourself only if it is a pure decorative duplicate.

CSS variables

NameDefaultDescription
--mv-reveal-mask-rCurrent radius (registered, animatable property).
--mv-reveal-mask-durationvar(--mv-duration-slower)Circle opening duration.

Accessibility

Both layers stay in the accessibility tree, so screen readers read the visible text and the hidden text (give the element an aria-label when the pair needs a name). The pointer effect runs only under (hover: hover) and (pointer: fine), and the system cursor is never hidden. Keyboard: when nothing inside is focusable the element becomes a tab stop (role group), and keyboard focus, on it or on a link inside the hidden layer, opens the layer fully; Tab away closes it. Touch: a tap toggles the whole layer, no press-and-drag needed. Reduced motion (or no fine pointer): no tracking and no trail, the layer fades in on hover, tap or focus. Forced colors: the focus ring uses Highlight. Still offer revealed through a visible button when the second layer matters.

Esta página se tradujo con IA. Informar de un problema de traducción