react-cropper + mui with the rotate and flip option

react-cropper + mui with the rotate and flip option

Table of contents

Demo & Code : https://codesandbox.io/s/react-cropper-js-mui-with-rotate-and-flip-option-y7mm6n

Download code from : https://github.com/krishnapankhania/react-mui-cropper-demo

Cropper Demo

How to use?

Create react project. More stuff on react docs.

npx create-react-app my-app
cd my-app
npm start

Let's install Material Ui react components, because we are going to use that

https://mui.com

Run below command

npm install @mui/material @emotion/react @emotion/styled

Let's install react cropper

Run below command

npm i react-cropper

We will create 3 components

  • Upload: File input.
  • Popup: A popup to open after the file is selected
  • CropperDemo: Cropper logic resides here

Upload.js

This component will take a file input from the user and will return file data to the parent component.

import * as React from "react";
import { styled } from "@mui/material/styles";
import Button from "@mui/material/Button";

const Input = styled("input")({
  display: "none"
});

export default function Upload({ getUploadedFile }) {
  const onChange = (e) => {
    e.preventDefault();
    let files;
    if (e.dataTransfer) {
      files = e.dataTransfer.files;
    } else if (e.target) {
      files = e.target.files;
    }
    if (files.length === 0) {
      return alert("Please select a file.");
    }
    const reader = new FileReader();
    reader.onload = () => {
      getUploadedFile(reader.result);
    };
    reader.readAsDataURL(files[0]);
  };
  return (
    <label htmlFor="contained-button-file">
      <Input
        accept="image/*"
        id="contained-button-file"
        multiple
        type="file"
        onChange={onChange}
      />
      <Button variant="contained" component="span">
        Upload
      </Button>
    </label>
  );
}

Popup.js

This component is a MUI dialog component that will hold the cropper.

import * as React from "react";
import Dialog from "@mui/material/Dialog";
import DialogContent from "@mui/material/DialogContent";
import DialogContentText from "@mui/material/DialogContentText";
import DialogTitle from "@mui/material/DialogTitle";
import CropperDemo from "./Cropper";

export default function Popup({ open, image, handleClose, getCroppedFile }) {
  return (
    <div>
      <Dialog
        open={open}
        onClose={handleClose}
        aria-labelledby="alert-dialog-title"
        aria-describedby="alert-dialog-description"
      >
        <DialogTitle id="alert-dialog-title">Crop Image</DialogTitle>
        <DialogContent>
          <DialogContentText id="alert-dialog-description">
            <CropperDemo
              handleClose={handleClose}
              src={image}
              getCroppedFile={getCroppedFile}
            />
          </DialogContentText>
        </DialogContent>
      </Dialog>
    </div>
  );
}

Cropper.js

This component does the cropping of an image and returns it to parent. Check the rotate and flip options as well. you can play around with other options too.

Check out available options

import React, { useRef, useState } from "react";
import Cropper from "react-cropper";
import Skeleton from "@mui/material/Skeleton";
import Button from "@mui/material/Button";
import ButtonGroup from "@mui/material/ButtonGroup";
import Box from "@mui/material/Box";

import "cropperjs/dist/cropper.css";

export default function CropperDemo({ src, getCroppedFile }) {
  const cropperRef = useRef(null);
  const [loading, setLoading] = useState(true);
  const [scaleX, setScaleX] = useState(1);
  const [scaleY, setScaleY] = useState(1);

  const handleClick = () => {
    const imageElement = cropperRef?.current;
    const cropper = imageElement?.cropper;
    const img = cropper.getCroppedCanvas().toDataURL();
    getCroppedFile(img);
  };
  const rotate = () => {
    const imageElement = cropperRef?.current;
    const cropper = imageElement?.cropper;
    cropper.rotate(90);
  };
  const flip = (type) => {
    const imageElement = cropperRef?.current;
    const cropper = imageElement?.cropper;
    if (type === "h") {
      cropper.scaleX(scaleX === 1 ? -1 : 1);
      setScaleX(scaleX === 1 ? -1 : 1);
    } else {
      cropper.scaleY(scaleY === 1 ? -1 : 1);
      setScaleY(scaleY === 1 ? -1 : 1);
    }
  };
  return (
    <>
      {loading && (
        <Skeleton variant="rectangular" width={"100%"} height={400} />
      )}
      <Box display={"flex"} justifyContent={"flex-end"} mb={1}>
        <ButtonGroup disableElevation variant="contained">
          <Button onClick={rotate}>Rotate</Button>
          <Button onClick={() => flip("h")}>Flip H</Button>
          <Button onClick={() => flip("v")}>Flip V</Button>
        </ButtonGroup>
      </Box>

      <Cropper
        src={src}
        style={{ height: 400, width: "100%" }}
        // Cropper.js options
        initialAspectRatio={16 / 9}
        guides={false}
        ready={() => {
          setLoading(false);
        }}
        ref={cropperRef}
      />
      <Button
        sx={{
          float: "right",
          mt: 1
        }}
        onClick={handleClick}
        autoFocus
        color="success"
        variant="contained"
      >
        Crop
      </Button>
    </>
  );
}

App.js

Let's use all components.

import * as React from "react";
import Box from "@mui/material/Box";
import Upload from "./Upload";
import Popup from "./Popup";
export default function App() {
  const [open, setOpen] = React.useState(false);
  const [image, setImage] = React.useState(
    "https://images.unsplash.com/photo-1612232134966-a9b076b9fbe7?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1170&q=80"
  );

  const handleClose = () => {
    setOpen(false);
  };

  return (
    <div>
      <Box
        sx={{
          display: "flex",
          flexDirection: "column",
          alignItems: "center"
        }}
      >
        <Box my={2}>
          <img src={image} alt="cropped" height={400} />
        </Box>

        <Upload
          getUploadedFile={(image) => {
            setOpen(true);
            setImage(image);
          }}
        />
        <Popup
          open={open}
          handleClose={handleClose}
          image={image}
          getCroppedFile={(image) => {
            setImage(image);
            handleClose();
          }}
        />
      </Box>
    </div>
  );
}