React Image Cropping

·

4 min read

Image cropping is one of the handiest features that come along with image uploads. Depending upon the application you are building, you may want to crop the image before you upload the images to some image storage services. We will discuss how to integrate image crop into your react application.

We will be making use of react-image-crop library for this purpose. The cropper component (ReactCrop) takes in some props, we will be focusing on some necessary props given below,

  • src - Image source (can also pass in blob URL).
  • crop - Initial crop state.
  • onChange - A callback that happens for every change of the crop (drag/resize).
  • onComplete - A callback that happens after a resize, drag, or nudge.
  • onImageLoaded - A callback that passes image DOM element when source image is loaded.

Both onChange and onComplete callback passes the current crop state object on trigger.

<ReactCrop
   src={upImg}
   onImageLoaded={onLoad}
   crop={crop}
   onChange={(crop) => setCrop(crop)}
   onComplete={(crop) => setCompletedCrop(crop)}
/>

The crop state object includes,

  • width - Width of crop image
  • height - Height of crop image
  • x, y - Cordinates of top-left corner of crop image from to that of original image
  • unit - Unit of crop length (eg: %, px)
  • aspect - Aspect ratio (eg: 16/9, 4/3, 1/1)

You can get some vague idea of change in crop state on drag from screenshots below,

crop-value.gif

The example below illustrated the dragging and resizing events using the Cropper component for fixed aspect ratio 1/1.

DraggingResizing
darg-crop.gifresize-crop.gif

Now we have the crop state object, we have the necessary details required to crop the image. We can either do cropping client-side or server-side. Sometime we can also try cropping client-side and if fails we can send image directly without cropping and crop image server-side. If you wish to crop server-side, you can make use of sharp library for a better experience. We will take about how to do client-side cropping using canvas API. We can draw the cropped image in canvas with the crop state with drawImage method,

function setCanvasImage(image, canvas, crop) {
  if (!crop || !canvas || !image) {
    return;
  }

  const scaleX = image.naturalWidth / image.width;
  const scaleY = image.naturalHeight / image.height;
  const ctx = canvas.getContext('2d');
  // refer https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio
  const pixelRatio = window.devicePixelRatio;

  canvas.width = crop.width * pixelRatio * scaleX;
  canvas.height = crop.height * pixelRatio * scaleY;

  // refer https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/setTransform
  ctx.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0);
  ctx.imageSmoothingQuality = 'high';

  // refer https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/drawImage
  ctx.drawImage(
    image,
    crop.x * scaleX,
    crop.y * scaleY,
    crop.width * scaleX,
    crop.height * scaleY,
    0,
    0,
    crop.width * scaleX,
    crop.height * scaleY
  );
}

Once we have cropped image drawn in canvas we can download cropped image using toBlob method,

function generateDownload(canvas, crop) {
  if (!crop || !canvas) {
    return;
  }

  canvas.toBlob(
    (blob) => {
      const previewUrl = window.URL.createObjectURL(blob);

      const anchor = document.createElement('a');
      anchor.download = 'cropPreview.png';
      anchor.href = URL.createObjectURL(blob);
      anchor.click();

      window.URL.revokeObjectURL(previewUrl);
    },
    'image/png',
    1
  );
}

Now we will look at how to integrate Cropper into you react application. First we ask the user to select an image file from their computer using input element and after the image is selected we load the selected image onto the cropper. After that we let the user select an image crop state either by dragging or resizing. And when onCompleteCrop event is triggered we draw the cropped image with crop state onto the canvas. Now either we let the user download the cropped image or send the image to the backend server for upload purposes.

export default function App() {
  const [upImg, setUpImg] = useState();

  const imgRef = useRef(null);
  const previewCanvasRef = useRef(null);

  const [crop, setCrop] = useState({ unit: 'px', width: 30, aspect: 1 });
  const [completedCrop, setCompletedCrop] = useState(null);

  console.log(crop);

  // on selecting file we set load the image on to cropper
  const onSelectFile = (e) => {
    if (e.target.files && e.target.files.length > 0) {
      const reader = new FileReader();
      reader.addEventListener('load', () => setUpImg(reader.result));
      reader.readAsDataURL(e.target.files[0]);
    }
  };

  const onLoad = useCallback((img) => {
    imgRef.current = img;
  }, []);

  useEffect(() => {
    setCanvasImage(imgRef.current, previewCanvasRef.current, completedCrop);
  }, [completedCrop]);

  return (
    <div className='App'>
      <div>
        <input type='file' accept='image/*' onChange={onSelectFile} />
      </div>
      <ReactCrop
        src={upImg}
        onImageLoaded={onLoad}
        crop={crop}
        onChange={(crop) => setCrop(crop)}
        onComplete={(crop) => setCompletedCrop(crop)}
      />
      <div>
        {/* Canvas to display cropped image */}
        <canvas
          ref={previewCanvasRef}
          style={{
            width: Math.round(completedCrop?.width ?? 0),
            height: Math.round(completedCrop?.height ?? 0),
          }}
        />
      </div>

    </div>
  );
}

You can access the full source code for crop implementation in react - aseerkt/react-image-crop-demo.

Resources

Hope you were able to react crop onto your application. See you soon with some new content.