When building an application, best practice requires reducing an image’s surrounding noise, thereby directing a user’s attention to a specific part of the image. Image cropping is a method for manipulating images to remove any unwanted elements. By changing the aspect ratio or orientation, we can draw viewers’ eyes to the photograph’s main subject and improve the overall composition. This is applicable for profile pictures or when we’re uploading certain images with specific dimensions.
In this article, we’ll compare four of the top React image cropping libraries, React Avatar Editor, react-cropper, react-image-crop, and react-easy-crop, evaluating each in terms of performance, popularity, and developer experience.
First, we’ll build an image uploader application in React, which we’ll use with each library. By the end of this tutorial, you should be able to choose the right fit for your project. To follow along with this tutorial, you’ll need:
- Basic knowledge of React and JavaScript
- Node.js and npm installed on your machine
And here’s what we’ll cover:
- Initialize a new React app with Create React App
Button
componentCheckbox
component- Creating our
modal
component - react-avatar-editor
- react-image-crop
- react-cropper
- react-easy-crop
Let’s get started!
You can follow along by accessing the project’s code.
Initialize a new React app with Create React App
We’ll start by initializing a new React app and bootstrapping it with Create React App; run the command below in your terminal to create a new React application:
npx create-react-app image uploader
Next, we’ll navigate to our project directory and run the following command to start our development server:
cd image uploader && yarn start
The command above will open a tab in our browser and display the application’s default boilerplate. Next, we’ll install dependencies, which we’ll include in our three cropper libraries.
Dependencies
We’ll install the following dependencies in our application:
- react-cropper:
cropperjs
as React component - React Image Crop: an image cropping tool for React with zero dependencies
- react-avatar-editor: a lightweight library cropping tool with zero dependencies
- react-easy-crop: a React component for easily cropping images
- styled-components: a component for writing CSS in our JavaScript
- Tailwind CSS: utility classes for our cropper components
- react-image-crop: another cropping library option with zero dependencies
Next, we’ll build components for our components. In our application, we’ll need a button
component to upload images and a modals
component, which will upload, save, and dismiss images from our uploader. Lastly, for each of our croppers, we’ll need a modalWrapper
.
Button
component
The Button
component is a skeleton component for all the buttons in our application.
First, we’ll use the style component to create a style guide for our buttons. Let’s create a components
folder in our application’s src
directory. Inside of your components
folder, create a new directory called Button
; inside this folder, create a Button. jsx
file and add the code below:
import styled from "styled-components"; const Button = ({ children, className, ...props }) => { return ( <StyledButton style={{ background: "skyblue", color: "#000", }} className={`${className} block text-black px-6 rounded-md font-semibold hover:opacity-75 transition-opacity duration-500 ease-in`} type="button" {...props} > {children} </StyledButton> ); };
In the code block above, we created a Button
component and passed children
, className
, and props
. Now, we’ll add styles to our button with styled-components:
const StyledButton = styled.button` background-color: #2eff7b; border: none; outline: none; height: 45px; &:focus { border: none; outline: none; } &:disabled { opacity: 1; cursor: not-allowed; } `; export default Button;
Checkbox
component
Now, we’ll build a Checkbox
component that links each image cropper. First, create a new folder called Checkbox
in our components directory. Inside, we’ll create a new file called Checkbox.jsx
and add the following code:
import styled from "styled-components"; const Checkbox = ({ label, onChange, id, isChecked }) => { return ( <Wrapper> <label htmlFor={id}>{label}</label> <input id={id} type="checkbox" name={label} onChange={() => { onChange(!isChecked ? label : null); }} value={isChecked ? label : ""} checked={isChecked} /> <span className="rounded-full" /> </Wrapper> ); };
In the code block above, we created a Checkbox
component that has a label and an ID inside, which we’ll use to select our cropper library. Next, we’ll build a modal
component for opening and closing a particular cropper library.
Creating our modal
component
Our modal component will feature props like onModalClose
, showModal
, and onSaveHandler
. We’ll also add a button for uploading and saving our cropped images.
In our components
directory, let’s create a new folder called Modal
. Inside, create a new file called Modal.jsx
and add the code block below:
import { createPortal } from "react-dom"; import styled from "styled-components"; import Button from "../Button/Button"; const Modal = ({ children, onModalClose, showModal, onSaveHandler }) => { return createPortal( <Wrapper style={{ opacity: showModal ? 1 : 0, pointerEvents: showModal ? "all" : "none", }} > <div onClick={onModalClose} role="button" className="iu-modal-backdrop" style={{ display: showModal ? "flex" : "none", }} /> <div className="iu-modal-content"> {children} <footer className="px-12 md:sticky absolute bottom-0 bg-white w-full left-0 py-4 border-t border-black flex items-center justify-between"> <Button onClick={onModalClose}>Dismiss</Button> <Button onClick={() => { onSaveHandler(); onModalClose(); }} > Save </Button> </footer> </div> </Wrapper>, document.getElementById("modal") ); };
In the code block above, we initialized a modal
component and created a Wrapper
component, where we added a function to upload and save an image.
react-avatar-editor
react-avatar-editor is an avatar and image cropper for React applications. With an intuitive UI, react-avatar-editor can easily crop, resize, and rotate images.
With 85k weekly downloads on npm and 1.8k stars on GitHub, react-avatar-editor is one of the most popular cropper libraries for React applications. To see react-avatar-editor in action, first, we’ll need to install it in our app with the command below:
//Yarn yarn add react-avatar-editor //npm npm install --save react-avatar-editor
Next, create a new folder called ReactAvatarEditor
. Inside, create a new file called ReactAvatarEditor.jsx
and add the code below:
import { useRef } from "react"; import AvatarEditor from "react-avatar-editor"; import styled from "styled-components"; import Modal from "../../Modal/Modal"; const ReactAvatarEditor = ({ showModal, onModalClose, imgURL, onSaveHandler, }) => { const EditorRef = useRef(null); const showCroppedImage = async () => { if (EditorRef.current) { const img = EditorRef.current.getImage().toDataURL(); return img; } }; return ( <Modal showModal={showModal} onSaveHandler={async () => onSaveHandler(await showCroppedImage())} onModalClose={onModalClose} > <Wrapper className="w-full h-full flex flex-col items-center justify-center"> <AvatarEditor ref={EditorRef} image={imgURL} width={250} height={250} border={0} scale={1.2} color={[255, 255, 255, 0.6]} /> </Wrapper> </Modal> ); };
In the code above, we imported the useRef
Hook, initialized our cropper packager as AvatarEditor
, and imported styled-components to add styles to our application.
Then, we initialized a functional component with AvatarEditor
and passed some methods from our modal
component. Finally, we assigned the ref
to our AvatarEditor
to get our image, convert it to a URL, and parse it for cropping.
To render our cropped images, we created a wrapper component and added props like image
, which is the URL of the image we want to crop. Note that width
, height
, border
, and color
refer to the editor’s attributes.
react-image-crop
react-image-crop is an open source library that allows us to crop images. One thing to know is that it has no dependency, meaning it’s lightweight. To begin, let’s install react-image-crop
:
//Using yarn yarn add react-image-crop //using npm npm i react-image-crop
Next, in our component let’s import the ReactCrop
component from react-image-crop
:
import { useState } from "react"; import ReactCrop from "react-image-crop"; import "react-image-crop/dist/ReactCrop.css";
Then we’ll create variables with useState
hooks:
const [image, setImage] = useState(null); const [crop, setCrop] = useState({ unit: "%", width: 30, aspect: 16 / 9 }); const [croppedImageUrl, setCroppedImageUrl] = useState("");
crop
contains our dimensions, image
contains our image file, and croppedImageUrl
contains the final cropped image.
We’re now going to add two functions (makeClientCrop
and getCroppedImg
) that will help us return our cropped image with setCroppedImageUrl()
:
const Cropper = ({ imgURL }) => { const makeClientCrop = async (crop) => { if ((image, crop.width && crop.height)) { const croppedImg = await getCroppedImg(image, crop, "newFile.jpeg"); setCroppedImageUrl(croppedImg); } }; const getCroppedImg = (sourceImage, crop, fileName) => { const canvas = document.createElement("canvas"); const scaleX = sourceImage.naturalWidth / sourceImage.width; const scaleY = sourceImage.naturalHeight / sourceImage.height; canvas.width = crop.width; canvas.height = crop.height; const ctx = canvas.getContext("2d"); ctx.drawImage( sourceImage, crop.x * scaleX, crop.y * scaleY, crop.width * scaleX, crop.height * scaleY, 0, 0, crop.width, crop.height ); try { return new Promise((resolve) => { canvas.toBlob((file) => { resolve(URL.createObjectURL(file)); }, "image/jpeg"); }); } catch (error) { console.log(error); return null; } }; }
Let’s add ReactCrop
to our component:
<ReactCrop src={imgURL} crop={crop} ruleOfThirds onImageLoaded={ (img) => { console.log(img); setImage(img); }} onComplete={(crop) => image? makeClientCrop(crop): console.log("wait")} onChange={(cropData) => setCrop(cropData)} />
On initial load, we feed the src
with our imgUrl
. Once ReactCrop
loads the image, it assigns it into our variable using setImage
. When there are any dimension changes, it assigns the data using setCrop
.
Also, onComplete
checks if the image
exists. If it does, it’ll trigger our makeClientCrop
function with the cropped image: cropData
.
react-cropper
react-cropper is an open source React wrapper component for Cropper.js, a JavaScript library that includes a photo editor and an image cropper. react-cropper has more that 63k weekly downloads on npm and 1.6k stars on GitHub. To use react-cropper in your application, first, install it with the command below:
// Using yarn yarn add react-cropper // Using npm npm install --save react-cropper
Next, add the following code block to crop an image using our image uploader application:
import { useRef, useState } from "react"; import Cropper from "react-cropper"; import "cropperjs/dist/cropper.css"; import Modal from "../../Modal/Modal"; const ReactCropper = ({ showModal, onModalClose, imgURL, onSaveHandler }) => { const cropperRef = useRef(null); const [croppedImg, setCroppedImg] = useState(""); const onCrop = () => { const imageElement = cropperRef?.current; const cropper = imageElement?.cropper; setCroppedImg(cropper.getCroppedCanvas().toDataURL()); }; return ( <Modal showModal={showModal} onSaveHandler={() => onSaveHandler(croppedImg)} onModalClose={onModalClose} > <Cropper src={imgURL} style={{ height: 500, width: "732px" }} initialAspectRatio={16 / 9} guides={false} crop={onCrop} ref={cropperRef} viewMode={1} // guides={true} minCropBoxHeight={10} minCropBoxWidth={10} // background={false} responsive={true} autoCropArea={1} aspectRatio={4 / 3} checkOrientation={false} /> </Modal> ); }; export default ReactCropper;
Similar to react-avatar-editor, we imported the useRef
and useState
Hooks from React. Next, we initialized a functional component,ReactCropper
, which takes in a number of props like the image URL and modal
.
To crop our image, we connected to the image cropper properties, like the image URL, then parsed it. Using React’s useState
Hook, we created a state for our cropped image.
To render our image, we initialized a Cropper
component and passed a few props from Cropper.js
, including src
for parsing the image URL. Finally, we pass styles to the style
prop.
More great articles from LogRocket:
- Don't miss a moment with The Replay, a curated newsletter from LogRocket
- Learn how LogRocket's Galileo cuts through the noise to proactively resolve issues in your app
- Use React's useEffect to optimize your application's performance
- Switch between multiple versions of Node
- Discover how to animate your React app with AnimXYZ
- Explore Tauri, a new framework for building binaries
- Advisory boards aren’t just for executives. Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
react-easy-crop
react-easy-crop is an open source React component that features a clean UI for cropping images and videos. react-easy-crop is mobile-friendly, offering crop dimensions in pixels and percentages and interactions for dragging and zooming.
On npm, react-easy-crop is currently downloaded more than 125k times a week. On GitHub, react-easy-crop has 1.4k stars. You can get started with react-easy-crop by installing the library using a package manager, as shown in the code block below:
//Yarn yarn add react-easy-crop // npm npm install react-easy-crop --save
To use react-easy-crop, you’ll need to wrap it in a Cropper
component tag. Let’s build a cropper component using the library below:
import { useCallback, useState } from "react"; import Cropper from "react-easy-crop"; import Modal from "../../Modal/Modal"; const ReactEasyCrop = ({ showModal, onModalClose, imgURL, onSaveHandler }) => { const [crop, setCrop] = useState({ x: 2, y: 2 }); const [zoom, setZoom] = useState(1); const [croppedArea, setCroppedArea] = useState(""); const onCropComplete = useCallback((croppedArea, croppedAreaPixels) => { setCroppedArea(croppedAreaPixels); }, []); const showCroppedImage = useCallback(async () => { try { const croppedImage = await getCroppedImg(imgURL, croppedArea, 0); return croppedImage; } catch (error) { console.error(error); } }, [croppedArea, imgURL]);
In the code block above, we created a component called ReactEasyCrop
, which contains props like showModal
, onModalClose
, imgURL
, onSaveHandler
, and showModal
.
Like other image croppers, the showModal
and onModalClose
props are used for uploading an image, while the imgUrl
provides the URL of the image we are uploading.
onCropComplete
allows us to save a cropped area of an image and specify the pixel or percentage size of the cropped image. Next, we’ll render our application using the Modal
wrapper component, as shown below:
return ( <Modal showModal={showModal} onSaveHandler={async () => onSaveHandler(await showCroppedImage())} onModalClose={onModalClose} > <div className="relative w-full"> <Cropper image={imgURL} crop={crop} zoom={zoom} aspect={4 / 3} onCropChange={setCrop} onCropComplete={onCropComplete} onZoomChange={setZoom} /> </div> </Modal> ); }; export default ReactEazyCrop;
In the code block above, we wrapped our entire application in a Modal
component, allowing us to upload our images. Next, we initialized the Cropper
component, where we passed the URL of our cropped images.
We also added the crop
prop, which is used to determine the position of the image to be cropped. zoom
is used to zoom in on the image, and the default is set to 1
.
onCropChange
is used to update our application’s crop state. onCropComplete
is called when a user stops zooming on an image.
Conclusion
Regardless of the type of project you’re working on, a cropper library can help you improve your overall UI by removing unwanted areas from a photograph. The libraries featured in this tutorial have thorough built-in features and are easily customizable.
Now, you should be able to choose the right cropper library for your application. I hope you enjoyed this tutorial!
Get setup with LogRocket's modern React error tracking in minutes:
- Visit https://logrocket.com/signup/ to get an app ID.
- Install LogRocket via NPM or script tag.
LogRocket.init()
must be called client-side, not server-side. - (Optional) Install plugins for deeper integrations with your stack:
- Redux middleware
- ngrx middleware
- Vuex plugin
$ npm i --save logrocket
// Code:
import LogRocket from 'logrocket';
LogRocket.init('app/id');
Add to your HTML:
<script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script>
<script>window.LogRocket && window.LogRocket.init('app/id');</script>
Great article Fortune!
I cloned the repo from github and tried to run it, but getting this error: “Unhandled Rejection (TypeError): image is null”, inside “getCroppedImg” function in “src/components/Croppers/ReactImageCrop/ReactImageCrop.jsx:21” file. Am I doing something wrong here? please help.
It got me error about wrapper component and async keyword
Line 7:10: ‘Wrapper’ is not defined
‘async’ is not defined
For me it only work using Node 16