Image Compare

Free

A before/after wipe that admits when it is not one: it measures both images' intrinsic sizes and says so when their aspect ratios disagree, because then part of what the reader sees as "the change" is the crop. Operated as a real ARIA slider — arrows, page keys, Home and End — which almost no implementation of this control is.

</>sv-image-compare imagecomparebefore-aftersliderdiff

Complexity tier: Interactive The primitive bar, plus declared async states, a keyboard model, focus restoration and undo for destructive actions.

import { SvImageCompareComponent } from '@styloviz/image-compare';

Demo & usage

Basic usage

Usage example — open the gallery for the full interactive demo.

An interactive demo for this component lives in the gallery.

Open live demo

More examples

Corner captions

example.htmlAngular
<sv-image-compare [beforeSrc]="a" [afterSrc]="b" beforeLabel="2019" afterLabel="2024" />

Controlled position

example.htmlAngular
<sv-image-compare [(position)]="wipe" [beforeSrc]="a" [afterSrc]="b" />

Inspect — the measured ratios

example.htmlAngular
<sv-image-compare variant="inspect" [beforeSrc]="a" [afterSrc]="b" />

React to a mismatch

example.htmlAngular
<sv-image-compare [beforeSrc]="a" [afterSrc]="b" (mismatch)="onNotComparable($event)" />

Alignment landmarks — the check the component cannot make

It compares DIMENSIONS, never content. Two files of identical size can still be different crops of the same scene — a tighter lens, a nudged horizon — and no measurement available to a browser will tell. Landmarks give the reader the thing the component cannot compute: a fixed line drawn once over the whole box, above both layers, that does not move with the wipe. A feature sitting on the line in one image and beside it in the other proves the frames do not match. crosshair is the centre, thirds is the rule-of-thirds grid, grid is quarters.

example.htmlAngular
<sv-image-compare [beforeSrc]="a" [afterSrc]="b" landmarks="thirds" />

A quarter turn is not a reframing

"Different shapes" is a symptom, not a diagnosis. A pair whose ratios are reciprocal — 4000×3000 against 3000×4000 — is one file carrying an EXIF orientation tag the other does not, and the fix is to re-export, not to re-crop. The warning names which of the four cases it is: identical, scaled (the wipe is honest, but the smaller one is being upscaled and will look softer), rotated, or genuinely reshaped.

example.htmlAngular
<sv-image-compare variant="inspect" [beforeSrc]="a" [afterSrc]="b" />
<!-- the inspect read-out carries the verdict as data-sv-verdict -->

When to use

  • Showing a retouch, a restoration, a render change or a before/after of the same frame.
  • The two images are the same scene at the same framing — that is the only case a wipe means anything.
  • For two genuinely different pictures, put them side by side; a wipe would imply a correspondence that is not there.

Async states

None — this component has nothing to load, so it has no loading, empty or error state to drive.

Inputs

NameTypeDefaultDescription
variantImageCompareVariant'slider'Presentation: `'slider'` (draggable handle), `'bare'` (no handle; keyboard and drag still work) or `'inspect'` (adds the measured ratios).
beforeSrcstring''The image revealed on the left of the wipe.
afterSrcstring''The image revealed on the right of the wipe.
beforeAltstring'the before image'Alt text, used in the slider's spoken value as well as on the images. "Before" and "after" are what the CONTROL does, not what the images are. A screen reader user hearing "50% after" learns nothing; "50% retouched" is the sentence a sighted reader gets from looking.
afterAltstring'the after image'Alt text for the after image, also used as its name in the slider's spoken value.
beforeLabelstring''Corner captions. Empty hides them.
afterLabelstring''Corner caption on the after image; empty hides it.
ariaLabelstring'Comparison position'Accessible name for the slider itself.
allowMismatchbooleanfalseSuppress the aspect-ratio warning. Provided because a caller who has deliberately letterboxed both images to a common frame is telling the truth and the warning would be noise. It is NOT a way to silence a real mismatch, and the docs say so.
landmarksImageCompareLandmarks'none'Draw a fixed alignment reference over both images.
positionnumber (two-way)0.5Wipe position, 0–1. Two-way.

Outputs

EventPayload typeDescription
mismatchnumberEmitted once when the two images turn out not to be comparable.
positionChangenumberEmits the new `position` whenever it changes — the output half of `[(position)]`.

Customization & theming

  • Three variants: `slider`, `bare` (no handle drawn; keyboard and drag still work) and `inspect`.
  • Two-way `position`, 0–1.
  • Corner captions, alt text and an accessible name for the slider.
  • `mismatch` fires once with the size of the difference; `allowMismatch` suppresses the on-screen warning for a deliberately letterboxed pair.
  • `landmarks` (`none` / `crosshair` / `thirds` / `grid`) draws a fixed reference over both images at once. Two-tone strokes, so the line survives both a white sky and a black shadow.
  • `svFrames()` is exported and classifies a pair without rendering anything: `identical`, `scaled`, `rotated`, `reshaped` or `unknown`, each with the sentence that fits it.

Best practices

  • Do: Export both images at the same aspect ratio. If you cannot, letterbox them to a common frame and set `allowMismatch`.
  • Do: Write `beforeAlt` / `afterAlt` as what the images ARE ("the original", "the retouch"), not what the control does — they become the slider's spoken value.
  • Tip: Use `beforeLabel` / `afterLabel` for dates or version names; they read at a glance where alt text does not.
  • Don't: Don't set `allowMismatch` to silence a real mismatch. It suppresses the message, not the measurement — the `inspect` read-out still shows the ratios.
  • Do: Turn on `landmarks` whenever the framing matters. Matching dimensions do not prove matching framing, and this is the only check available for the difference.
  • Do: Read a `rotated` verdict as an export problem. Re-cropping a picture whose only fault is an orientation tag makes it worse.
  • Don't: Don't assume a clean warning means the comparison is sound. Silence here means the dimensions agree — nothing more.

Accessibility

  • A real `role="slider"` with `aria-valuenow` and an `aria-valuetext` naming the two images, so a screen reader announces what is showing rather than a bare percentage.
  • Arrows nudge by 1%, Page keys by 10%, Home and End go to the extremes — and every other key is left alone, so Tab still moves on rather than being trapped.
  • The stacked copy is `aria-hidden`, so the pair is announced once instead of as two pictures.
  • The aspect warning is rendered above the images as a `role="status"`, because the failure it describes is invisible: the widget looks right and the difference it shows is real, just the wrong difference.
  • The landmark overlay is `aria-hidden` and `pointer-events-none` — it is a visual aid for a check the component cannot make, and it must neither be read out as content nor eat the drag.

Feedback on Image Compare

Spotted a gap in this page, or something that didn't work as described? Comments are GitHub Discussions in the public styloviz/community repo, so posting needs a GitHub account.

Loads giscus.app and GitHub — nothing is fetched until you click.