Image Cropper

Pro

Crop, with the parts that silently corrupt the result handled: EXIF orientation, the browser's canvas ceiling, and a quality setting PNG ignores. An image cropper fails by producing a picture rather than an error, so it reports what the crop will actually produce rather than what was selected.

</>sv-image-cropper imagecropeditoravatarcanvas

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

import { SvImageCropperComponent } from '@styloviz-pro/image-editor';

Demo & usage

Basic usage

Pro

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

Pro

An interactive demo for this component lives in the gallery.

Open live demo

More examples

A square avatar crop

Pro

What the crop will actually produce

Pro

The plan is exposed rather than applied silently: a crop past what a browser will render is downscaled, and past that ceiling a canvas is not an error — it is blank, and toBlob succeeds. The warnings are the only thing standing between the user and a transparent file.

A photograph that is not stored the way it is shown

Pro

A phone photograph is stored sideways with a tag saying which way is up. The component cannot read that tag — it lives in the file's bytes and an <img> does not expose it — so a host that decoded the file passes it in. Leave it at 1 for an image the browser has already auto-oriented.

The geometry on its own

Pro

Exported so a host with its own surface — an inline crop inside an uploader, a canvas editor — gets the same arithmetic rather than a second copy that drifts.

When to use

  • Letting a user choose the part of an image you will store — an avatar, a cover, a thumbnail.
  • You need the crop in the image's own pixels, not the ones on screen.
  • For a before/after wipe rather than a selection, use Image Compare.

Async states

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

Inputs

NameTypeDefaultDescription
srcstring''Image URL. Anything an `<img>` accepts, including a blob: or data: URL.
altstring''Alt text for the image being cropped.
aspectnumber0width ÷ height. 0 leaves the crop free.
minSizenumber24Smallest crop, in displayed pixels.
orientationSvExifOrientation1The image's EXIF orientation, when the host knows it. THE COMPONENT CANNOT READ THIS ITSELF, AND PRETENDING OTHERWISE WOULD BE THE LIE. The tag lives in the file's bytes; an `<img>` element does not expose it. A host that decoded the file already has it, and one that did not should leave this at 1 — which is correct for every image a browser has already auto-oriented, and that is most of them today.
outputTypeSvImageType'image/png'Format of the exported image: `'image/png'`, `'image/jpeg'` or `'image/webp'`.
qualitynumber0.92Encoder quality from 0 to 1 for JPEG and WebP exports; PNG ignores it.
flattenTostring'#ffffff'The colour transparency is flattened onto for a format with no alpha. Only JPEG needs it, and only JPEG gets it. A canvas starts transparent and JPEG has no alpha channel, so without a fill a logo on a transparent ground exports as artwork on BLACK — a valid file, no error, and the classic silent corruption. White is the default because it is what printing does and what a user expects.
maxDimensionnumber | undefinedundefinedLongest side the export may have, in px; a larger crop is scaled down. Unset uses the browser canvas limit (16,384).
maxPixelsnumber | undefinedundefinedTotal pixel budget for the export; a larger crop is scaled down to fit. Unset uses the browser canvas limit (16,777,216).
disabledbooleanfalseLocks the crop box and its resize handles against pointer and keyboard edits.

Outputs

EventPayload typeDescription
cropChangeSvCropRectThe crop, in SOURCE pixels, whenever it changes.
croppedBlobThe rendered image, once `crop()` resolves.
cropFailed{ reason: string; error?: unknown }An export that could not be produced, with the reason.

Customization & theming

  • Lock the shape with `aspect`, bound it with `minSize`, and pick the encoding with `outputType` and `quality`; `maxDimension` and `maxPixels` override the conservative canvas ceilings.
  • `flattenTo` (default `'#ffffff'`) is the colour a JPEG export is filled with before the draw — JPEG has no alpha, so a transparent ground would otherwise come out black. PNG and WebP keep their transparency and are never filled.
  • Read the selection with `cropChange` (source pixels) or `sourceCrop()`, the rendering with `plan()` and `outputLabel()`, and produce the image with `cropImage()`.
  • `svDefaultCrop`, `svResizeCrop`, `svMoveCrop`, `svClampCrop`, `svCropToSource`, `svOrientedSize`, `svOrientationTransform` and `svOutputPlan` are exported for a host building its own surface.

Best practices

  • Do: Show `plan().warnings`. A crop the browser cannot render is downscaled, and past that ceiling the canvas is blank rather than an error — `toBlob` still calls back with a valid, fully transparent file.
  • Do: Pass `orientation` when you decoded the file yourself and know the EXIF tag. A portrait phone photo is stored landscape, and cropping the stored orientation lands on a region that is not in the picture.
  • Do: Use JPEG or WebP when you want compression. `toBlob(cb, 'image/png', 0.6)` ignores the 0.6 silently, so a quality slider wired to a PNG export does nothing at all.
  • Don't: Don't send `cropChange` straight to a canvas without checking it is the SOURCE rect. The crop is dragged in display pixels; the emitted rect is already converted, and the two differ by whatever the browser laid the image out at.

Accessibility

  • The crop box is a focusable `role="group"` whose label reports what the crop will produce, and every one of the eight handles is a labelled button in the tab order — "drag a box" has no non-pointer equivalent unless one is built, so arrows nudge by a pixel, Shift by ten, and Escape resets.
  • Only the keys it handles are swallowed: Tab and everything else pass through, so the crop box cannot become a focus trap.
  • The dimming mask is `pointer-events-none` and `aria-hidden`, so it neither swallows a drag aimed at a handle nor appears in the accessibility tree.
  • What the crop will produce — including any downscale and its reason — is a `role="status"` caption rather than something the user has to infer from the file they get later.

Feedback on Image Cropper

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.