React Image Cropping

React Image Cropping

Learn how to integrate image cropping into your React.js application

·

5 min read

Image cropping is an essential feature for many applications that involve image uploads. It allows users to select and focus on specific parts of an image, ensuring the content is perfectly framed before being stored. Depending on your application’s requirements, you might want to enable users to crop images before uploading them to your image storage service. In this guide, we will explore how to seamlessly integrate image cropping 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,

  • 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.

  • aspect - Aspect ratio of cropper

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

import ReactCrop from 'react-image-crop'

// ...
    <ReactCrop
       crop={{ x: 0, y: 0, unit: 'px', height: 100, width: 50 }}
       onChange={(crop) => {}}
       onComplete={(crop) => {}}
       aspect={1}
    >
        <img ... />
    </ReactCrop>
//...

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)

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

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

Dragging

Resizing

Now that we have the crop state object, we possess all the necessary details to crop the image. Cropping can be done either client-side or server-side. Sometimes, it’s useful to attempt client-side cropping first and, if it fails, upload the image without cropping and handle it server-side. If you choose to crop server-side, the Sharp library offers an efficient solution. In this guide, we’ll focus on client-side cropping using the Canvas API. By utilizing the drawImage method, we can draw the cropped image onto the canvas based on the crop state,

// src/utils.js

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,

// src/utils.js

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
  );
}

Next, we'll explore how to integrate Cropper into your React application. First, prompt the user to select an image file from their computer using an input element. Once an image is selected, load it into the Cropper component. Allow the user to define the crop area by dragging or resizing the selection. When the onCompleteCrop event is triggered, draw the cropped image onto a canvas using the crop state. Finally, you can either let the user download the cropped image or upload it to your image storage service.

// src/App.jsx

import React, { useRef, useState } from 'react';
import ReactCrop from 'react-image-crop';
import { drawImageOnCanvas, generateDownload } from './utils';
import 'react-image-crop/dist/ReactCrop.css';

export default function App() {
  const [imgSrc, setImgSrc] = useState();
  const [crop, setCrop] = useState();
  const [completedCrop, setCompletedCrop] = useState(null);

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

  // load the selected image on to cropeer
  const handleFileSelect = (e) => {
    if (e.target.files && e.target.files.length > 0) {
      const reader = new FileReader();
      reader.addEventListener('load', () => setImgSrc(reader.result));
      reader.readAsDataURL(e.target.files[0]);
    }
  };

  // on crop selection, draw it on canvas
  const handleCompleteCrop = (crop) => {
    drawImageOnCanvas(imgRef.current, canvasRef.current, crop);
    setCompletedCrop(crop);
  };

  // download the cropped image drawn on canvas
  const handleDownload = () => {
    generateDownload(canvasRef.current, completedCrop);
  };

  // adjust canvas onto crop dimenstion
  const canvasStyles = {
    width: Math.round(completedCrop?.width ?? 0),
    height: Math.round(completedCrop?.height ?? 0),
  };

  return (
    <div className='App'>
      <div className='FileSelector'>
        <input
          type='file'
          accept='image/*'
          onChange={handleFileSelect}
        />
      </div>

      <div className='CropperWrapper'>
        <ReactCrop
          crop={crop}
          onChange={setCrop}
          aspect={1}
          onComplete={handleCompleteCrop}
        >
          {imgSrc && <img ref={imgRef} src={imgSrc} alt='cropper image' />}
        </ReactCrop>
        <div className='CanvasWrapper'>
          <canvas ref={canvasRef} style={canvasStyles} />
        </div>
      </div>
        <button
          type='button'
          disabled={!completedCrop}
          onClick={handleDownload}
        >
          Download cropped image
        </button>
    </div>
  );
}

Integrating image cropping into your React application enhances user experience by allowing precise image adjustments before upload. By leveraging the Cropper component, users can easily select and crop their images directly in the browser. With the crop state managed and the cropped image drawn onto a canvas, you have the flexibility to either provide the cropped image for download or upload it to your backend server. This approach ensures that images are perfectly framed and tailored to your application's needs, making the process seamless and efficient for users.

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

Resources

Hope you were able to integrate react cropper feature onto your application. See you soon with some new content. Stay tuned.