bêta

Image Compare <mv-image-compare>

Comparaison avant/après où tout le cadre sert de piste au curseur : une poignée en capsule role=slider entièrement utilisable au clavier, une pastille de valeur en direct, des badges coupés net par la ligne, un tactile qui distingue tapotements, glissements et défilement de la page, horizontal ou vertical, au glisser ou en suivi de la souris, un balayage facultatif et une variante lumineuse.

CatégorieMédias
TypeWeb Component (<mv-image-compare>)
Statutbêta
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

Installation

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

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

Fichiers copiés (dépendances comprises) : 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.

Utilisation

Balisage de référence : partez de celui-ci et personnalisez-le avec les attributs, data-* et les 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

NameTypeDefaultDescription
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

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

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