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
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
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>
);
}
Top comments (1)
is react-cropper library still maintained? last commit was last year? How do you think about use that on production?