beta

Image Compare <mv-image-compare>

Comparación antes/después en la que todo el marco es la pista del slider: un tirador en cápsula role=slider con compatibilidad total con teclado, un chip de valor en vivo, insignias cortadas limpiamente por la línea, gestos táctiles que distinguen toques, deslizamientos y desplazamientos de página, horizontal o vertical, arrastre o seguimiento del mouse, un barrido opcional y una variante con resplandor.

CategoríaMultimedia
TipoWeb Component (<mv-image-compare>)
Estadobeta
Keywordscompare, before-after, comparison, slider, image, diff, reveal

When to use

  • Before and after results must be compared on one image, like retouching, color grading or renovations
  • Two aligned versions of a render, design or any HTML content should be compared with a draggable, keyboard-operable divider
  • A hero demo should sweep automatically to show a transformation until the visitor takes over

Avoid when

  • A second layer should be uncovered by a circle that follows the cursor → use Reveal Mask instead
  • The two images are not aligned views of the same scene; show them side by side instead

Instalación

node scripts/add.mjs image-compare --out ./src/marvelous

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

Archivos copiados (dependencias incluidas): tokens/tokens.css, core/base.css, core/dom.js, core/element.js, core/motion.js, components/image-compare/image-compare.js, components/image-compare/image-compare.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(min(20rem,100%),1fr));gap:1.5rem;width:100%;max-width:62rem;align-items:start">
  <figure style="margin:0;display:grid;gap:.75rem">
    <mv-image-compare id="ic-demo-photo" label="Compare the raw and color-graded photo" before-label="Raw" after-label="Graded" value="42" autoplay style="aspect-ratio:12/7">
      <img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1200 700'%3E%3Cdefs%3E%3ClinearGradient id='s' x1='0' y1='0' x2='0' y2='1'%3E%3Cstop offset='0' stop-color='%231e1b4b'/%3E%3Cstop offset='.45' stop-color='%236d28d9'/%3E%3Cstop offset='.78' stop-color='%23f472b6'/%3E%3Cstop offset='1' stop-color='%23fdba74'/%3E%3C/linearGradient%3E%3ClinearGradient id='a' x1='0' y1='0' x2='0' y2='1'%3E%3Cstop offset='0' stop-color='%237c3aed'/%3E%3Cstop offset='1' stop-color='%23312e81'/%3E%3C/linearGradient%3E%3ClinearGradient id='b' x1='0' y1='0' x2='0' y2='1'%3E%3Cstop offset='0' stop-color='%233b0764'/%3E%3Cstop offset='1' stop-color='%231e1b4b'/%3E%3C/linearGradient%3E%3ClinearGradient id='w' x1='0' y1='0' x2='0' y2='1'%3E%3Cstop offset='0' stop-color='%23fb923c' stop-opacity='.85'/%3E%3Cstop offset='.5' stop-color='%239d174d' stop-opacity='.7'/%3E%3Cstop offset='1' stop-color='%231e1b4b'/%3E%3C/linearGradient%3E%3CradialGradient id='g'%3E%3Cstop offset='0' stop-color='%23fff7ed'/%3E%3Cstop offset='.3' stop-color='%23fde68a'/%3E%3Cstop offset='1' stop-color='%23fb923c' stop-opacity='0'/%3E%3C/radialGradient%3E%3C/defs%3E%3Crect width='1200' height='470' fill='url(%23s)'/%3E%3Ccircle cx='760' cy='420' r='210' fill='url(%23g)'/%3E%3Ccircle cx='760' cy='430' r='54' fill='%23fff7ed'/%3E%3Cpath d='M0 390 140 250l60 45 40-30 120-100 70 90 40-30 90 70 60-50 90 110 110-130 70 60 40-20 70 60 50-60 130 110 V480 H0Z' fill='url(%23a)'/%3E%3Cpath d='M360 165 395 195 380 205 415 222 440 200 420 180Z M820 230 850 258 840 266 870 280 890 262Z' fill='%23f5f3ff' opacity='.75'/%3E%3Cpath d='M0 480 120 390l140 60 140-100 160 110 140-70 150 80 150-90 200 80 V480 H0Z' fill='url(%23b)'/%3E%3Crect y='470' width='1200' height='230' fill='url(%23w)'/%3E%3Cg fill='%23fde68a'%3E%3Crect x='640' y='500' width='240' height='5' rx='2.5' opacity='.7'/%3E%3Crect x='680' y='530' width='160' height='4' rx='2' opacity='.5'/%3E%3Crect x='710' y='560' width='100' height='3' rx='1.5' opacity='.4'/%3E%3Crect x='735' y='590' width='50' height='3' rx='1.5' opacity='.3'/%3E%3C/g%3E%3Cpath d='M0 700 V610 Q180 570 360 625 T760 645 T1200 600 V700Z' fill='%230b0720'/%3E%3Cg fill='%230b0720'%3E%3Cpath d='M90 610 110 520 130 610Z M130 615 155 500 180 615Z M1040 600 1062 505 1084 600Z M1080 605 1100 530 1120 605Z'/%3E%3C/g%3E%3C/svg%3E" alt="Sunset over a mountain lake, raw file with dull colors" loading="lazy" style="filter:grayscale(.72) contrast(.78) brightness(1.1) sepia(.12)">
      <img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1200 700'%3E%3Cdefs%3E%3ClinearGradient id='s' x1='0' y1='0' x2='0' y2='1'%3E%3Cstop offset='0' stop-color='%231e1b4b'/%3E%3Cstop offset='.45' stop-color='%236d28d9'/%3E%3Cstop offset='.78' stop-color='%23f472b6'/%3E%3Cstop offset='1' stop-color='%23fdba74'/%3E%3C/linearGradient%3E%3ClinearGradient id='a' x1='0' y1='0' x2='0' y2='1'%3E%3Cstop offset='0' stop-color='%237c3aed'/%3E%3Cstop offset='1' stop-color='%23312e81'/%3E%3C/linearGradient%3E%3ClinearGradient id='b' x1='0' y1='0' x2='0' y2='1'%3E%3Cstop offset='0' stop-color='%233b0764'/%3E%3Cstop offset='1' stop-color='%231e1b4b'/%3E%3C/linearGradient%3E%3ClinearGradient id='w' x1='0' y1='0' x2='0' y2='1'%3E%3Cstop offset='0' stop-color='%23fb923c' stop-opacity='.85'/%3E%3Cstop offset='.5' stop-color='%239d174d' stop-opacity='.7'/%3E%3Cstop offset='1' stop-color='%231e1b4b'/%3E%3C/linearGradient%3E%3CradialGradient id='g'%3E%3Cstop offset='0' stop-color='%23fff7ed'/%3E%3Cstop offset='.3' stop-color='%23fde68a'/%3E%3Cstop offset='1' stop-color='%23fb923c' stop-opacity='0'/%3E%3C/radialGradient%3E%3C/defs%3E%3Crect width='1200' height='470' fill='url(%23s)'/%3E%3Ccircle cx='760' cy='420' r='210' fill='url(%23g)'/%3E%3Ccircle cx='760' cy='430' r='54' fill='%23fff7ed'/%3E%3Cpath d='M0 390 140 250l60 45 40-30 120-100 70 90 40-30 90 70 60-50 90 110 110-130 70 60 40-20 70 60 50-60 130 110 V480 H0Z' fill='url(%23a)'/%3E%3Cpath d='M360 165 395 195 380 205 415 222 440 200 420 180Z M820 230 850 258 840 266 870 280 890 262Z' fill='%23f5f3ff' opacity='.75'/%3E%3Cpath d='M0 480 120 390l140 60 140-100 160 110 140-70 150 80 150-90 200 80 V480 H0Z' fill='url(%23b)'/%3E%3Crect y='470' width='1200' height='230' fill='url(%23w)'/%3E%3Cg fill='%23fde68a'%3E%3Crect x='640' y='500' width='240' height='5' rx='2.5' opacity='.7'/%3E%3Crect x='680' y='530' width='160' height='4' rx='2' opacity='.5'/%3E%3Crect x='710' y='560' width='100' height='3' rx='1.5' opacity='.4'/%3E%3Crect x='735' y='590' width='50' height='3' rx='1.5' opacity='.3'/%3E%3C/g%3E%3Cpath d='M0 700 V610 Q180 570 360 625 T760 645 T1200 600 V700Z' fill='%230b0720'/%3E%3Cg fill='%230b0720'%3E%3Cpath d='M90 610 110 520 130 610Z M130 615 155 500 180 615Z M1040 600 1062 505 1084 600Z M1080 605 1100 530 1120 605Z'/%3E%3C/g%3E%3C/svg%3E" alt="The same sunset after color grading, vivid colors" loading="lazy">
    </mv-image-compare>
    <figcaption style="display:flex;justify-content:space-between;gap:1rem;font-size:.85rem;color:var(--mv-fg-muted)">
      <span>Lake Annecy · “Dusk” preset</span>
      <output id="ic-demo-out" style="font-variant-numeric:tabular-nums">42%</output>
    </figcaption>
  </figure>
  <figure style="margin:0;display:grid;gap:.75rem">
    <mv-image-compare orientation="vertical" mode="hover" data-variant="glow" label="Compare the wireframe and the final UI" before-label="Wireframe" after-label="Final UI" style="aspect-ratio:4/3">
      <div aria-hidden="true" style="display:grid;grid-template-rows:auto 1fr auto;gap:12px;padding:18px;background:var(--mv-bg-subtle);font:500 11px/1 var(--mv-font-mono);color:var(--mv-fg-subtle)">
        <div style="display:flex;gap:8px;align-items:center"><span style="width:26px;height:26px;border:1.5px dashed currentColor;border-radius:6px"></span><span style="flex:1;height:10px;border:1.5px dashed currentColor;border-radius:4px;max-width:120px"></span><span style="width:70px;height:26px;border:1.5px dashed currentColor;border-radius:6px"></span></div>
        <div style="display:grid;grid-template-columns:1.2fr 1fr;gap:12px"><div style="border:1.5px dashed currentColor;border-radius:10px;display:grid;place-items:center">IMAGE</div><div style="display:grid;gap:8px;align-content:center"><span style="height:14px;border:1.5px dashed currentColor;border-radius:4px"></span><span style="height:14px;width:70%;border:1.5px dashed currentColor;border-radius:4px"></span><span style="height:8px;width:90%;background:currentColor;opacity:.3;border-radius:4px"></span><span style="height:8px;width:80%;background:currentColor;opacity:.3;border-radius:4px"></span><span style="height:28px;width:60%;border:1.5px dashed currentColor;border-radius:8px;margin-top:6px"></span></div></div>
        <div style="display:flex;gap:8px"><span style="flex:1;height:34px;border:1.5px dashed currentColor;border-radius:8px"></span><span style="flex:1;height:34px;border:1.5px dashed currentColor;border-radius:8px"></span><span style="flex:1;height:34px;border:1.5px dashed currentColor;border-radius:8px"></span></div>
      </div>
      <div aria-hidden="true" style="display:grid;grid-template-rows:auto 1fr auto;gap:12px;padding:18px;background:var(--mv-surface);font:13px/1.3 var(--mv-font-sans);color:var(--mv-fg)">
        <div style="display:flex;gap:8px;align-items:center"><span style="width:26px;height:26px;border-radius:7px;background:linear-gradient(135deg,var(--mv-accent),color-mix(in oklch,var(--mv-accent),white 40%))"></span><strong style="flex:1">Lumen Studio</strong><span style="padding:6px 12px;border-radius:999px;background:var(--mv-accent);color:var(--mv-fg-on-accent);font-size:12px;font-weight:600">Book now</span></div>
        <div style="display:grid;grid-template-columns:1.2fr 1fr;gap:12px"><div style="border-radius:10px;background:radial-gradient(circle at 70% 35%,#fde68a 0 12%,transparent 13%),linear-gradient(180deg,#6d28d9,#f472b6 70%,#fdba74)"></div><div style="display:grid;gap:6px;align-content:center"><strong style="font-size:17px;letter-spacing:-.01em">Mountain photo workshop</strong><span style="color:var(--mv-fg-muted);font-size:12px">3 days · Annecy · 8 spots</span><span style="color:var(--mv-fg-muted);font-size:12px">Golden light, lakes and peaks.</span><span style="justify-self:start;margin-top:6px;padding:7px 12px;border-radius:8px;border:1px solid var(--mv-border-strong);font-size:12px;font-weight:600">View itinerary</span></div></div>
        <div style="display:flex;gap:8px"><span style="flex:1;padding:8px 10px;border-radius:8px;background:var(--mv-bg-muted);font-size:11px"><b style="display:block;font-size:14px">4.9★</b>reviews</span><span style="flex:1;padding:8px 10px;border-radius:8px;background:var(--mv-bg-muted);font-size:11px"><b style="display:block;font-size:14px">$640</b>all-inclusive</span><span style="flex:1;padding:8px 10px;border-radius:8px;background:var(--mv-accent-subtle);color:var(--mv-accent-fg);font-size:11px"><b style="display:block;font-size:14px">May 12</b>departure</span></div>
      </div>
    </mv-image-compare>
    <figcaption style="font-size:.85rem;color:var(--mv-fg-muted)">Vertical, mode="hover": the line follows a mouse; touch and keyboard still drag.</figcaption>
  </figure>
</div>
<script type="module">
  const ic = document.getElementById("ic-demo-photo");
  const out = document.getElementById("ic-demo-out");
  ic.addEventListener("mv-input", (e) => { out.textContent = `${Math.round(e.detail.value)}%`; });
</script>

API

Attributes

NameTipoDefaultDescription
valuenumber50Divider position (0-100, share of “before” kept visible).
modedrag | hoverdragdrag: press or drag anywhere; hover: the divider follows a mouse on fine-pointer devices (touch and keyboard always drag).
orientationhorizontal | verticalhorizontalhorizontal: before on the left, vertical line; vertical: before on top, horizontal line.
before-label / after-labelstringCorner badges, clipped by the line with their layer; also used in aria-valuetext.
labelstringCompare before and afterAccessible name of the slider.
autoplaybooleanGently sweeps until the first interaction; holds still under the mouse, pauses off-screen, off with reduced motion.
data-variantglowAccent-tinted line with a soft halo.

Properties

NameTipoDescription
positionnumberCurrent position (read/write, no event).
MvImageCompare.strings{ label, before, after }Default English wording, to translate once for every instance.

Events

NameDescription
mv-inputWhile the position changes from the user (detail: { value }).
mv-changeWhen a drag, tap, key press or mouse-follow ends on a new position (detail: { value }).

Content structure

NameDescription
1st child“Before” layer (img, picture, video or any content).
2nd child“After” layer, revealed on the right (or bottom).

CSS classes

NameDescription
mv-image-compare-before / -after / -divider / -handle / -knob / -value / -labelParts (added to the children or generated).

CSS variables

NameDefaultDescription
--mv-image-compare-linewhiteLine and knob color.
--mv-image-compare-line-width2pxLine thickness.
--mv-image-compare-knob-size2.75remKnob length (the capsule is 64% as wide); the hit area never drops below 44px.
--mv-image-compare-radiusvar(--mv-radius-xl)Frame corner radius.

Accessibility

The handle is a focusable role=slider with aria-valuenow, aria-valuetext (“Raw 42%, Graded 58%”) and aria-orientation. Keys: ←/→ (↑/↓ also) ±1, Shift ±10, PageUp/PageDown ±10, Home/End; in vertical, ↑ and PageUp move the line up. Both layers keep their own alt text; the badges and value chip are aria-hidden. Touch: a swipe along the axis drags, a tap jumps, a swipe across it scrolls the page; the thumb hit area is at least 44px. Reduced motion: no sweep and no glide. Forced colors: system-colored line, knob and focus ring. Hover effects and mouse-follow only apply to fine pointers; the system cursor is never hidden.

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