DEV Community

Sharon Gomez
Sharon Gomez

Posted on

Cropping and resizing images in React

In my last post I wrote about how I struggled with images clogging up my Firebase Database/Storage. One of the ways that helped decrease the file size was by allowing users to crop their image and then resizing it before it was uploaded to my Firebase Storage. Here's how I set up basic image cropping using the react-easy-crop library.

Install react-easy-crop

Using npm:

npm install react-easy-crop --save
Enter fullscreen mode Exit fullscreen mode

or using yarn:

yarn add react-easy-crop
Enter fullscreen mode Exit fullscreen mode

Set up ImageCropper component

Here's my basic setup for the cropper. I'm using getBlob() in order to pass the cropped image up from this child component.

Setting aspect={1} will force the image to be cropped into a square, but you can change the aspect ratio to whatever you want. For me, I kept the aspect ratio to 1 as I'm using it for user avatars in my app, and square images are easier to style. 😊

// ImageCropper.js

import React, { useState } from 'react'
import Cropper from 'react-easy-crop'
import { getCroppedImg } from './cropImage'

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

    /* onCropComplete() will occur each time the user modifies the cropped area, 
    which isn't ideal. A better implementation would be getting the blob 
    only when the user hits the submit button, but this works for now  */
    const onCropComplete = async (_, croppedAreaPixels) => {
        const croppedImage = await getCroppedImg(
            inputImg,
            croppedAreaPixels
        )
        getBlob(croppedImage)
    }

    return (
        /* need to have a parent with `position: relative` 
    to prevent cropper taking up whole page */
        <div className='cropper'> 
            <Cropper
                image={inputImg}
                crop={crop}
                zoom={zoom}
                aspect={1}
                onCropChange={setCrop}
                onCropComplete={onCropComplete}
                onZoomChange={setZoom}
            />
        </div>
    )
}

export default ImageCropper
Enter fullscreen mode Exit fullscreen mode

Set up a component that takes the image file

The cropper takes in an image url or base64. Here I allowed the user to upload their own image and then converted it to base64.

// ImageUpload.js

import React, { useState } from 'react'
import * as firebase from 'firebase/app'
import ImageCropper from './ImageCropper'

const ImageUpload = () => {
    const [blob, setBlob] = useState(null)
    const [inputImg, setInputImg] = useState('')

    const getBlob = (blob) => {
        // pass blob up from the ImageCropper component
        setBlob(blob)
    }

    const onInputChange = (e) => {
        // convert image file to base64 string
        const file = e.target.files[0]
        const reader = new FileReader()

        reader.addEventListener('load', () => {
            setInputImg(reader.result)
        }, false)

        if (file) {
            reader.readAsDataURL(file)
        }
    }

    const handleSubmitImage = (e) => {
    // upload blob to firebase 'images' folder with filename 'image'
        e.preventDefault()
        firebase
            .storage()
            .ref('images')
            .child('image')
            .put(blob, { contentType: blob.type })
            .then(() => {
                // redirect user 
            })
    }


    return (
        <form onSubmit={handleSubmitImage}>
            <input
                type='file'
                accept='image/*'
                onChange={onInputChange}
            />
            {
                inputImg && (
                    <ImageCropper
                        getBlob={getBlob}
                        inputImg={inputImg}
                    />
                )
            }
            <button type='submit'>Submit</button>
        </form>
    )
}

export default ImageUpload
Enter fullscreen mode Exit fullscreen mode

Set up function to crop and save the image

To save only the 'cropped' section of the image, we create a canvas and use .useContext('2d') to create a 2d shape on it. We draw only the cropped section of the image on our canvas using .drawImage(), and then return the canvas as a blob.

Set the canvas.width and canvas.height to however big you want to store the cropped image as (in pixels). For me, I kept it to 250px by 250px.

// cropImage.js

// create the image with a src of the base64 string
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 const getCroppedImg = async (imageSrc, crop) => {
    const image = await createImage(imageSrc)
    const canvas = document.createElement('canvas')
    const ctx = canvas.getContext('2d')

    /* setting canvas width & height allows us to 
    resize from the original image resolution */
    canvas.width = 250
    canvas.height = 250

    ctx.drawImage(
        image,
        crop.x,
        crop.y,
        crop.width,
        crop.height,
        0,
        0,
        canvas.width,
        canvas.height
    )

    return new Promise((resolve) => {
        canvas.toBlob((blob) => {
            resolve(blob)
        }, 'image/jpeg')
    })
}
Enter fullscreen mode Exit fullscreen mode

This should leave you with a working image cropping tool! When a user uploads an image, the cropper will appear. The user is then able to drag the cropped area and zoom in/out to their content. When they hit submit, the final cropped image is then uploaded (in my case to Firebase Storage) and resized to reduce the file size.

Here's how mine looks after a bit of styling:

A screenshot of the image cropping tool in action.

Thanks for reading! 😊

Top comments (16)

Collapse
 
valentinhervieu profile image
Valentin Hervieu

React-easy-crop author here, thanks for this really cool tutorial 🎉

Collapse
 
sharong profile image
Sharon Gomez

hey thanks so much! it's a super useful repo haha 😄

Collapse
 
trakyrichard profile image
Traky Richard BLM

please can we have the repository of the project?

Collapse
 
sharong profile image
Sharon Gomez
Collapse
 
trakyrichard profile image
Traky Richard BLM

Thanks

Collapse
 
shaancodes profile image
Shaan Alam

Hey, can you tell whether the picture retains its quality after cropping or not? Becuase, I have used other libraries and they seem to decrease the picture quality after cropping the image ... thanks !

Collapse
 
shubhamkhandodia profile image
shubhamkhandodia

Hey Sharon , I'll start off by saying that the article was really articulate and helpful , Although if it's ok could you please also attach the css that you used because i tried using it in a popup modal and the photo cropping component takes up the whole space and the submit button becomes invisible .

Collapse
 
isakzayane profile image
IsakZayane

Thank you for this! Really helped me out!

Collapse
 
sharong profile image
Sharon Gomez

glad i could be of some help! 😄

Collapse
 
yahayaa90199430 profile image
yahaya ahmad

thanks

Collapse
 
sharong profile image
Sharon Gomez

np, glad i could help! 😁

Collapse
 
ruanmiguel profile image
Ruan-Miguel

Nice article, thanks for sharing! I have build one crop plugin, check it out: jsuites.net/v3/image-cropper

Collapse
 
brunot profile image
Bruno

Amazing! I'm def using this on a project I'm working on.

Collapse
 
sharong profile image
Sharon Gomez

glad i could help! thanks for reading 😃

Collapse
 
blacksmoke26 profile image
Junaid Atari

I also created a standalone plugin,

You can try it out:
github.com/blacksmoke26/react-crop...

Collapse
 
ridoansaleh profile image
Ridoan

I found this has a better feature: github.com/react-cropper/react-cro...