Image Manipulation with react-easy-crop
Published: 2022-08-20 01:25:00

by [Clarence Bakosi](https://blog.openreplay.com/authors/clarence-bakosi)

Image manipulation involves altering an image using various methods and techniques to achieve desired results. This allows you to concentrate on an image aspect while ignoring others. As a result, the image aspect ratio, form, or size are reduced. Image manipulation plays an important role in front-end apps.

The [react-easy-crop](https://www.npmjs.com/package/react-easy-crop) package is a free open-source JavaScript library. It supports image formats (JPEG, PNG, and GIF) as URL or [base64](https://en.wikipedia.org/wiki/Base64) string and video formats supported in HTML5. It's an efficient tool for manipulating images/videos in front-end apps.

In this article, our focus will be on manipulating images with react-easy-crop. We will create a simple file upload application. The result of the cropped area will be displayed using the following functionalities supported by react-easy-crop:

- Drag feature
- Aspect ratio
- Cropping
- Zooming
- Crop shape
- Rotate interactions


## Creating our React app

To start, use the commands below to launch the React application.

```
 npx create-react-app easy-crop
 cd easy-crop
 npm i react-easy-crop
 npm i @material-ui/core
 npm start
```

Before we begin, we will modify `app.css` to add some styling to the application. Copy and paste the code block below into `app.css`.

```
.App {
  background-color: #c4c4c4;
  font-family: 'Helvetica Neue', sans-serif;
}

.App-header {
  padding-top: 20px;
  height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-start;
  color: #fff;
}

label {
  font-weight: bold;
  padding-left: 10px;
}

button {
  z-index: 2;
  cursor: pointer;
  width: 100%;
  background-color: #000;
  color: #fff;
  padding: 10px 0;
}

.cropped-image {
  height: 600px;
  width: auto;
}

.cropped-image-container {
  margin-top: 20px;
  display: flex;
  flex-direction: column;
}

._coverImage-holder  {
  padding: 25px 40px;
  background-color:black;
  border-radius: 5px;
  cursor: pointer;
  margin-bottom: 20px;
}


.container {
  display: flex;
  flex-direction: column;
  position: relative;
}

.crop-container {
  height: 600px;
  width: 600px;
}

.controls {
  display: flex;
  flex-direction: column;
  width: 600px;
  position: absolute;
  bottom: -15px;
}
```

## Setting up react-easy-crop

We'll create a component called `EasyCrop.js` and paste the code block below into it.

```javascript
import React, { useState } from "react";
import Cropper from 'react-easy-crop';

const EasyCrop = () => {
 const [crop, setCrop] = useState({ x: 0, y: 0 });

  return (
    <Cropper
      image="https://cdn.pixabay.com/photo/2016/07/07/16/46/dice-1502706__340.jpg"
      crop={crop}
      onCropChange={setCrop}
    />
  )
}

export default EasyCrop;
```


Before we dive into the functionalities that react-easy-crop supports, we will update our `App.js` component with the code block below.

```javascript
import EasyCrop from "./EasyCrop";

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <EasyCrop  />
      </header>
    </div>
  );
}

export default App;

```


The `drag` functionality is enabled by default. Moving forward, we will implement other functionalities react-easy-crop has to offer.

![1 Drag](https://blog.openreplay.com/f8531c4b29e42286baf3ebfccf02fd9d/image01.gif)


## Aspect Ratio
The aspect ratio represents the image's width and height. It can be manipulated by adding the `aspect` prop to the `Cropper` component.

The prop's value syntax is `width/height`, and its default value is `4/3`.

We will modify the value to `5/5` for our' Cropper' component.

```
<Cropper
  image="https://cdn.pixabay.com/photo/2016/07/07/16/46/dice-1502706__340.jpg"
  crop={crop}
  aspect={5 / 5}
  onCropChange={setCrop}
/>
```
![2 Aspect Ratio](https://blog.openreplay.com/static/f8e2e6d62f5e121538a8bc8b7243d321/412e4/image02.png)


## Crop Shape
The image shape can be changed by adding the `cropShape` prop. This prop accepts a string of `rect` or `round`. The default value is `rect`; to make the image rounded, we will change the value of `cropShape` to `round`.

```
<Cropper
  image="https://cdn.pixabay.com/photo/2016/07/07/16/46/dice-1502706__340.jpg"
  crop={crop}
  aspect={4 / 4}
  cropShape="round"
  onCropChange={setCrop}
/>
```

![3 Crop Shape](https://blog.openreplay.com/static/603a7bcaaa8510cb7dcd182fd9cb7e1e/412e4/image03.png)


## Zoom
To activate the zoom functionality, react-easy-crop offers five props. These props include `zoom`, `zoomWithScroll`, `zoomSpeed`, `minZoom`, and `maxZoom`.

```javascript
import React, { useState } from "react";
import Slider from "@material-ui/core/Slider";
import Cropper from "react-easy-crop";

const EasyCrop = () => {
  const [crop, setCrop] = useState({ x: 0, y: 0 });
  const [zoom, setZoom] = useState(1);

  return (
    <div>
      <div className="crop-container">
        <Cropper
          image="https://cdn.pixabay.com/photo/2016/07/07/16/46/dice-1502706__340.jpg"
          crop={crop}
          zoom={zoom}
          zoomSpeed={4}
          maxZoom={3}
          zoomWithScroll={true}
          showGrid={true}
          aspect={4 / 3}
          onCropChange={setCrop}
          onZoomChange={setZoom}
        />
      </div>
      <div className="controls">
        <label>
          Zoom
          <Slider
            value={zoom}
            min={1}
            max={3}
            step={0.1}
            aria-labelledby="zoom"
            onChange={(e, zoom) => setZoom(zoom)}
            className="range"
          />
        </label>
      </div>
    </div>
  );
};

export default EasyCrop;
```

![4 Zoom](https://blog.openreplay.com/3c565bffcd92d8580295f82068a647f5/image04.gif)



## Rotation
Rotation functionality can be added using two props: `rotation` and `onRotationChange`.
```javascript
import React, { useState } from "react";
import Slider from "@material-ui/core/Slider";
import Cropper from "react-easy-crop";

  const EasyCrop = () => {
  const [crop, setCrop] = useState({ x: 0, y: 0 });
  const [zoom, setZoom] = useState(1);
  const [rotation, setRotation] = useState(0);

  return (
     <div>
        <div className="crop-container">
          <Cropper
            image='https://cdn.pixabay.com/photo/2016/07/07/16/46/dice-1502706__340.jpg'
            crop={crop}
            rotation={rotation}
            zoom={zoom}
            zoomSpeed={4}
            maxZoom={3}
            zoomWithScroll={true}
            showGrid={true}
            aspect={4 / 3}
            onCropChange={setCrop}
            onZoomChange={setZoom}
            onRotationChange={setRotation}
          />
        </div>
        <div className="controls">
          <label>
            Rotate
            <Slider
              value={rotation}
              min={0}
              max={360}
              step={1}
              aria-labelledby="rotate"
              onChange={(e, rotation) => setRotation(rotation)}
              className="range"
            />
          </label>
        </div>
      </div>
  );
};

export default EasyCrop;
```
![5 Rotation](https://blog.openreplay.com/e5b6eab6c905235f03d2df80335239ba/image05.gif)



## Open Source Session Replay

_[OpenReplay](https://github.com/openreplay/openreplay) is an open-source, session replay suite that lets you see what users do on your web app, helping you troubleshoot issues faster. OpenReplay is self-hosted for full control over your data._

![replayer.png](https://static.openreplay.com/images/banner-blog.png)

_Start enjoying your debugging experience - [start using OpenReplay for free](https://github.com/openreplay/openreplay)._




## Display cropped image
Next, using the supported functionalities that react-easy-crop offers, we will implement the file upload feature into the application to make it dynamic. To do this, we will first create a component named `Crop.js` and paste the code block below.

```javascript
export const createImage = (url) =>
  new Promise((resolve, reject) => {
    const image = new Image();
    image.addEventListener("load", () => resolve(image));
    image.addEventListener("error", (error) => reject(error));
    image.setAttribute("crossOrigin", "anonymous"); 
    image.src = url;
  });

export function getRadianAngle(degreeValue) {
  return (degreeValue * Math.PI) / 180;
}

export function rotateSize(width, height, rotation) {
  const rotRad = getRadianAngle(rotation);

  return {
    width:
      Math.abs(Math.cos(rotRad) * width) + Math.abs(Math.sin(rotRad) * height),
    height:
      Math.abs(Math.sin(rotRad) * width) + Math.abs(Math.cos(rotRad) * height),
  };
}

export default async function getCroppedImg(
  imageSrc,
  pixelCrop,
  rotation = 0,
  flip = { horizontal: false, vertical: false }
) {
  const image = await createImage(imageSrc);
  const canvas = document.createElement("canvas");
  const ctx = canvas.getContext("2d");

  if (!ctx) {
    return null;
  }

  const rotRad = getRadianAngle(rotation);

  const { width: bBoxWidth, height: bBoxHeight } = rotateSize(
    image.width,
    image.height,
    rotation
  );

  // set canvas size to match the bounding box
  canvas.width = bBoxWidth;
  canvas.height = bBoxHeight;

  ctx.translate(bBoxWidth / 2, bBoxHeight / 2);
  ctx.rotate(rotRad);
  ctx.scale(flip.horizontal ? -1 : 1, flip.vertical ? -1 : 1);
  ctx.translate(-image.width / 2, -image.height / 2);

  ctx.drawImage(image, 0, 0);

  const data = ctx.getImageData(
    pixelCrop.x,
    pixelCrop.y,
    pixelCrop.width,
    pixelCrop.height
  );


  canvas.width = pixelCrop.width;
  canvas.height = pixelCrop.height;

  ctx.putImageData(data, 0, 0);

  return new Promise((resolve, reject) => {
    canvas.toBlob((file) => {
      resolve(URL.createObjectURL(file));
    }, "image/jpeg");
  });
}
```

The subsequent stage involves modifying the `EasyCrop.js` component, making the image prop dynamic.

```javascript
import { useCallback, useState } from "react";
import Slider from "@material-ui/core/Slider";
import Cropper from "react-easy-crop";
import getCroppedImg from "./Crop";

const EasyCrop = ({ image }) => {
  const [crop, setCrop] = useState({ x: 0, y: 0 });
  const [zoom, setZoom] = useState(1);
  const [rotation, setRotation] = useState(0);
  const [croppedAreaPixels, setCroppedAreaPixels] = useState(null);
  const [croppedImage, setCroppedImage] = useState(null);

  const onCropComplete = useCallback((croppedArea, croppedAreaPixels) => {
    setCroppedAreaPixels(croppedAreaPixels);
  }, []);

  const showCroppedImage = useCallback(async () => {
    try {
      const croppedImage = await getCroppedImg(
        image,
        croppedAreaPixels,
        rotation
      );
      console.log("donee", { croppedImage });
      setCroppedImage(croppedImage);
    } catch (e) {
      console.error(e);
    }
  }, [croppedAreaPixels, rotation, image]);

  const onClose = useCallback(() => {
    setCroppedImage(null);
  }, []);

  return (
    <div>
      <button
        style={{
          display: image === null || croppedImage !== null ? "none" : "block",
        }}
        onClick={showCroppedImage}
      >
        Crop
      </button>
      <div
        className="container"
        style={{
          display: image === null || croppedImage !== null ? "none" : "block",
        }}
      >
        <div className="crop-container">
          <Cropper
            image={image}
            crop={crop}
            rotation={rotation}
            zoom={zoom}
            zoomSpeed={4}
            maxZoom={3}
            zoomWithScroll={true}
            showGrid={true}
            aspect={4 / 3}
            onCropChange={setCrop}
            onCropComplete={onCropComplete}
            onZoomChange={setZoom}
            onRotationChange={setRotation}
          />
        </div>
        <div className="controls">
          <label>
            Rotate
            <Slider
              value={rotation}
              min={0}
              max={360}
              step={1}
              aria-labelledby="rotate"
              onChange={(e, rotation) => setRotation(rotation)}
              className="range"
            />
          </label>
          <label>
            Zoom
            <Slider
              value={zoom}
              min={1}
              max={3}
              step={0.1}
              aria-labelledby="zoom"
              onChange={(e, zoom) => setZoom(zoom)}
              className="range"
            />
          </label>
        </div>
      </div>
      <div className="cropped-image-container">
        {croppedImage && (
          <img className="cropped-image" src={croppedImage} alt="cropped" />
        )}
        {croppedImage && <button onClick={onClose}>close</button>}
      </div>
    </div>
  );
};

export default EasyCrop;
```

Additionally, we make the final changes to the `App.js` component, including the file upload feature.

```javascript
import React, { useState } from "react";
import EasyCrop from "./EasyCrop";

function App() {
  const [image, setImage] = useState(null);

  const handleImageUpload = async (e) => {
    setImage(URL.createObjectURL(e.target.files[0]));
  };

  return (
    <div className="App">
      <header className="App-header">
        <label className="_coverImage-holder">
          Upload Image
          <input
            type="file"
            name="cover"
            onChange={handleImageUpload}
            accept="img/*"
            style={{ display: "none" }}
          />
        </label>
        <EasyCrop image={image}  />
      </header>
    </div>
  );
}

export default App;

```

![6 Final output](https://blog.openreplay.com/563d25f0de15673c9f1648bc16d08a98/image06.gif)





## Conclusion

React-easy-crop is efficient in manipulating images in React applications. It offers the flexibility to achieve desired results when altering images for the web. Here are links to the [source code](https://github.com/clarencepenz/easy-crop) and [live app](https://easy-crop.netlify.app/).

- [react-easy-crop](https://www.npmjs.com/package/react-easy-crop)
- [Github](https://github.com/ValentinH/react-easy-crop)


> A TIP FROM THE EDITOR: On the topic of working with images, our [React 18 - What's New and How it Will Benefit Developers](https://blog.openreplay.com/react-18-what-s-new-and-how-it-will-benefit-developers) article highlights some advantages of the latest version of React.


[![newsletter](https://blog.openreplay.com/static/e6b18eda7a327511296a2f27692f9c70/e9957/newsletter.png)](https://newsletter.openreplay.com/)