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.
Son
Plein écran
Pause Lecture
Catégorie Médias Type Web Component (<mv-image-compare>) Statut stable Keywords compare, 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
Obtenir Image Compare. Inclus dans toutes les offres : le Pro pack contient les 339 composants, avec la CLI et le serveur MCP utilisés ci-dessous.
Voir les offres
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/i18n.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">Halden 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
Name Type Par défaut Description valuenumber 50Divider position (0-100, share of “before” kept visible). modedrag | hover dragdrag: press or drag anywhere; hover: the divider follows a mouse on fine-pointer devices (touch and keyboard always drag). orientationhorizontal | vertical horizontalhorizontal: before on the left, vertical line; vertical: before on top, horizontal line. before-label / after-labelstring Corner badges, clipped by the line with their layer; also used in aria-valuetext. labelstring Compare before and afterAccessible name of the slider. autoplayboolean Gently sweeps until the first interaction; holds still under the mouse, pauses off-screen, off with reduced motion. data-variantglow Accent-tinted line with a soft halo.
Properties
Name Type Description positionnumber Current position (read/write, no event). MvImageCompare.strings{ label, before, after, valueText } Default English wording shared by every instance, kept for compatibility: prefer the strings property or setStrings(). stringsPartial<Record<string, string>> Overrides for the default text (en-US): label (slider label without a label attribute), before, after (side names without before-label / after-label), valueText ({before}, {value}, {after}, {rest}; the slider value text). App-wide translations: setStrings() from core/i18n.js, under the key "image-compare". Can be set before the element is defined.
Events
Name Description 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
Name Description 1st child“Before” layer (img, picture, video or any content). 2nd child“After” layer, revealed on the right (or bottom).
CSS classes
Name Description mv-image-compare-before / -after / -divider / -handle / -knob / -value / -labelParts (added to the children or generated).
CSS variables
Name Par défaut Description --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. At 0 and 100 the line sits on the frame edge while the knob and its focus ring stay whole inside the frame. 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.