Image Compare
FreeA 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.
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 demoMore examples
Corner captions
<sv-image-compare [beforeSrc]="a" [afterSrc]="b" beforeLabel="2019" afterLabel="2024" />
Controlled position
<sv-image-compare [(position)]="wipe" [beforeSrc]="a" [afterSrc]="b" />
Inspect — the measured ratios
<sv-image-compare variant="inspect" [beforeSrc]="a" [afterSrc]="b" />
React to a mismatch
<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.
<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.
<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
| Name | Type | Default | Description |
|---|---|---|---|
| variant | ImageCompareVariant | 'slider' | Presentation: `'slider'` (draggable handle), `'bare'` (no handle; keyboard and drag still work) or `'inspect'` (adds the measured ratios). |
| beforeSrc | string | '' | The image revealed on the left of the wipe. |
| afterSrc | string | '' | The image revealed on the right of the wipe. |
| beforeAlt | string | '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. |
| afterAlt | string | 'the after image' | Alt text for the after image, also used as its name in the slider's spoken value. |
| beforeLabel | string | '' | Corner captions. Empty hides them. |
| afterLabel | string | '' | Corner caption on the after image; empty hides it. |
| ariaLabel | string | 'Comparison position' | Accessible name for the slider itself. |
| allowMismatch | boolean | false | Suppress 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. |
| landmarks | ImageCompareLandmarks | 'none' | Draw a fixed alignment reference over both images. |
| position | number (two-way) | 0.5 | Wipe position, 0–1. Two-way. |
Outputs
| Event | Payload type | Description |
|---|---|---|
| mismatch | number | Emitted once when the two images turn out not to be comparable. |
| positionChange | number | Emits 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.