Guide
Moving from Cropper.js or react-image-crop
From Cropper.js
// before (v1)
const cropper = new Cropper(image, { aspectRatio: 1, viewMode: 1 });
const canvas = cropper.getCroppedCanvas({ width: 512 });
canvas.toBlob((blob) => upload(blob), 'image/jpeg', 0.9);
// after
import { createImageCropper } from '@devix-labs/image-cropper';
import '@devix-labs/image-cropper/styles.css';
const picker = createImageCropper(input, { aspect: 1, maxWidth: 512, type: 'image/jpeg', quality: 0.9 });
upload(await picker.toBlob());
| Cropper.js | Here |
|---|---|
new Cropper(imgElement, …) |
createImageCropper(fileInput, …) — it picks the picture too |
aspectRatio |
aspect, which also takes [min, max] |
viewMode: 1/2/3 |
mode: 'free' | 'fixed' |
getCroppedCanvas().toBlob() |
toBlob(), toFile(), toDataURL() |
getData() |
getCrop() — the same four numbers, in the original's pixels |
setData() |
setCrop() |
rotate(), scaleX(-1) |
rotate(), flip('x') |
destroy() |
destroy(), which also gives the input back |
cropper-* classes |
dxi-* classes, plus classNames for your own |
If you are on v1 and have been putting off v2 — its most-reacted open issue is
a request to keep supporting v1, with 35
votes, followed by two more about migrating and missing demos — this is a smaller move than that one,
and it ends with a File rather than a canvas.
The one-pixel black seam along a crop's edge (#551, +18) does not happen here; there is a test that reads every pixel of the output's edges.
From react-image-crop
// before
const [crop, setCrop] = useState();
<ReactCrop crop={crop} onChange={setCrop} aspect={1} circularCrop>
<img ref={imgRef} src={src} />
</ReactCrop>
// …then forty lines of canvas code to turn `crop` into a file,
// and another bug when the displayed size is not the natural size
// after
import { ImageCropper } from '@devix-labs/image-cropper/react';
<ImageCropper ref={cropper} aspect={1} round mode="fixed" maxWidth={512} />
const file = await cropper.current.toFile();
| react-image-crop | Here |
|---|---|
crop / onChange in percent or pixels of the displayed image |
getCrop() / onChange in pixels of the original — no scaling factor to get wrong |
aspect |
aspect, which also takes a [min, max] range |
circularCrop |
round — and the file is genuinely round, not just the preview |
minWidth, maxWidth on the crop |
minWidth, maxWidth on the crop; maxWidth on the output is separate |
| your own canvas code | toFile() |
a wrapped <img> you must load yourself |
a file input that opens, drops and pastes |
The four issues people keep opening — #380,
#523,
#566,
#535 — are all the same missing
function. It is toFile().
From react-easy-crop
// before
<Cropper image={src} crop={crop} zoom={zoom} aspect={1}
onCropChange={setCrop} onCropComplete={(area, pixels) => setPixels(pixels)} />
// …then getCroppedImg(src, pixels) copied from the README
// after
<ImageCropper ref={cropper} aspect={1} mode="fixed" />
const file = await cropper.current.toFile();
croppedAreaPixels becomes getCrop(), and the getCroppedImg helper the README asks you to copy
is toFile(). Blurriness when zoomed on Safari
(#520) does not arise, because the output
is drawn from the decoded original rather than from what is on screen.
From croppie
croppie was last published in 2020.
// before
const croppie = new Croppie(el, { viewport: { width: 200, height: 200, type: 'circle' } });
croppie.result({ type: 'blob', size: { width: 512, height: 512 } }).then(upload);
// after
const picker = createImageCropper(input, { mode: 'fixed', aspect: 1, round: true, width: 512, height: 512 });
upload(await picker.toBlob());
viewport + boundary become mode: 'fixed' with aspect and --dxi-stage-height; result()
becomes toBlob().