Skip to content
Devix Open Source
UI component v1.0.0 MIT Alpha

Image Cropper

Crop, resize and compress to WebP in the browser before upload.

npm install @devix-labs/image-cropper
Vanilla JS React Vue Svelte
Crop an avatar Open

toBlob(), toFile() and toDataURL(), drawn from the decoded original at its own resolution — the part every other cropper leaves to its users. No one-pixel seam where the crop meets an edge, because the canvas clips a transformed image instead of slicing at fractional coordinates. Round output through a real alpha mask. Aspect ratios as a number, as free, or as a [min, max] range. Full keyboard control with everything announced. Turn, flip, pinch and wheel zoom, a fixed avatar mode, EXIF orientation handled by the browser, and a layout that survives being built inside a closed modal. 8.2 kB gzip, zero dependencies, and the same behaviour in React, Vue, Svelte, a web component or a plain page.

What you get

It gives you the picture

toBlob(), toFile() and toDataURL(), at the resolution of the original. Four of react-image-crop's most-reacted open issues are people trying to do this themselves.

No black seam

The whole image is drawn under a transform and the canvas clips it, so there is no fractional slice to sample past. Cropper.js has had that seam open for years; a test here reads every pixel of the edges.

Round really is round

An alpha mask, so the corners of the file are transparent — not a rounded preview over a square image, which is its own open bug elsewhere.

An aspect range

aspect: [0.5, 2] leaves the crop exactly as drawn while it is between them, and holds only at the ends. Asked for on react-image-crop and still open.

A keyboard can use it

The crop is a focusable named group: arrows move it, shift resizes it, alt is finer, ctrl is coarser, and every change is read out. None of the others can be used without a mouse.

It survives a modal

A hidden element measures zero. A ResizeObserver lays the widget out the moment it appears, and getCrop() says null rather than 0 × 0 until then.

Phone photos the right way up

EXIF orientation is applied by the browser, once. Libraries that parse the tag themselves end up rotating the picture twice.

An honest output size

maxWidth and maxHeight only ever scale down, because enlarging invents detail that was never captured — and the widget shows the size you are about to get while you drag.

Image Picker & Cropper — overview

Why it exists

Every popular cropper hands you a rectangle and stops.

On react-image-crop — 1.8 million downloads a week — four of the most-reacted open issues are people trying to turn that rectangle into something they can upload, and a fifth is that its circular crop produces a square file. react-easy-crop, at 2.4 million a week, has the same gap and a README that asks you to copy forty lines of canvas code. Cropper.js has 13.9 thousand stars, a one-pixel black seam that has been open for years, and a v2 rewrite whose most-reacted issue is a request to keep supporting v1.

And not one of them can be used without a mouse.

What it does differently

  • It gives you the picture. toBlob(), toFile(), toDataURL() — drawn from the decoded original at its own resolution, so a crop of a 12-megapixel photograph is as sharp as the photograph.
  • No seam. The whole image is drawn under a transform and the canvas clips it, instead of slicing a source rectangle at fractional coordinates and sampling past the edge. A test walks every pixel of the output's top and left edges.
  • Round is a mask, so the corners of the file are actually transparent.
  • An aspect range, [min, max], which leaves the crop exactly as drawn between its ends.
  • A keyboard. The crop is a focusable named group: arrows move it, shift resizes it, alt is finer, ctrl is coarser, and every change is read out.
  • It survives a modal. A hidden element is 0×0; a ResizeObserver lays the widget out the moment it appears, and getCrop() says null rather than a nonsense rectangle until then.
  • EXIF by not parsing EXIF. The browser applies the orientation tag; libraries that parse it themselves end up applying it twice.
  • The file input stays the control, so choosing a picture works with a keyboard and a screen reader with no code at all.
  • 8.2 kB, no dependencies.

Not in 1.0

Filters, brightness and colour adjustment, and free-angle straightening are an image editor, which is a different product with a different weight. Server-side cropping is a few lines with the exported maths — getCrop() returns four numbers in the original's own pixel space — but a Node build of the renderer would need a canvas implementation, and that is a dependency this has managed without.

How it compares

Questions

Why does every other cropper make me write canvas code?

Because they are crop-selection widgets rather than croppers: they report a rectangle and stop. Doing the rest correctly means drawing from the decoded original rather than the on-screen copy, not slicing at fractional coordinates, masking a circle rather than rounding a corner, and letting the browser apply EXIF once. That is what toFile() does here.

What do I get back?

A Blob, a File or a data URL — whichever you ask for — plus getCrop(), which is the crop as four numbers in the original's own pixel space. Keeping those four numbers alongside the file lets a server reproduce the same crop at any size later.

Does it work for avatars?

mode: 'fixed' with aspect: 1 and round: true. The crop is a circular hole in the middle and the picture moves under it, and the picture can never pull away from behind it, however far you zoom out.

Can it resize and compress for upload?

That is what maxWidth, type and quality do. A 512-pixel WebP from a 12-megapixel phone photograph is a few tens of kilobytes, so the upload is quick and the server has nothing left to resize.

Does it edit photos — filters, brightness, straightening?

No, and deliberately. That is an image editor, which is a different product with a different weight. This turns and flips by ninety degrees, crops, and resizes.