效果
去cropper.js官网查看下载使用
- npm 安装使用
- 添加上传按钮,图片显示,配置cropper参数,(有使用其他ui组件库可以使用自己的ui),使用原生或自定义不用隐藏input直接绑定onClick使用即可,不用使用下面ui button
- 使用
import React, { useEffect, useRef, useState } from 'react';
import Button from '@material-ui/core/Button';
import imageCompression from 'browser-image-compression';
import Cropper from 'cropperjs';
import 'cropperjs/dist/cropper.min.css';
interface IProps {
onCrop?: (base64: string) => void;//返回裁剪后图片
avatarImage?: string | undefined | null; //传入已有图片
rotateBut?: boolean;//是否加载旋转
}
const ImageCropper: React.FC<IProps> = ({ onCrop, avatarImage, rotateBut = false }) => {
const imageRef = useRef<HTMLImageElement>(null);
const cropperRef = useRef<Cropper | null>(null);
const [imageSrc, setImageSrc] = useState('');
const [preImgUrl, setPreImgUrl] = useState('');
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0];
if (!file) return;
const reader = new FileReader();
reader.onload = e => {
setImageSrc(e.target?.result as string);
};
reader.readAsDataURL(file);
};
const debounce = (func: Function, wait: number) => {
let timeout: NodeJS.Timeout | null = null;
return function() {
if (timeout !== null) {
clearTimeout(timeout);
}
timeout = setTimeout(() => {
func();
}, wait);
};
};
useEffect(() => {
if (avatarImage) {
setImageSrc(avatarImage);
}
}, [avatarImage]);
const onChangeCrop = () => {
if (cropperRef?.current?.getCropBoxData()) {
const croppedCanvas = cropperRef?.current?.getCroppedCanvas().toDataURL('image/png');
croppedCanvas && setPreImgUrl(croppedCanvas);
} else {
console.error('请先选择裁剪区域!');
}
};
const dd = debounce(onChangeCrop, 500);
useEffect(() => {
if (imageSrc && imageRef.current) {
cropperRef.current = new Cropper(imageRef.current, {
aspectRatio: 1,//裁剪框比例
autoCropArea: 0.8,//裁剪框显示
rotatable: true,//图片旋转
dragMode: 'move',//图片移动
// minCropBoxWidth: 100,//裁剪框宽
// minCropBoxHeight: 100,
movable: true,
crop() {
dd();
},
});
}
return () => {
if (cropperRef.current) {
cropperRef.current.destroy();
cropperRef.current = null;
}
};
}, [imageSrc]);
const handleRotate = () => {
if (cropperRef.current) {
cropperRef.current.rotate(90);
}
};
const handleCrop = async () => {
if (cropperRef.current) {
cropperRef.current.getCroppedCanvas().toBlob(async blob => {
if (blob) {
const options = {
maxSizeMB: 0.2,
maxWidthOrHeight: 1920,
useWebWorker: true,
};
const compressedFile = await imageCompression(blob as File, options);
const reader = new FileReader();
reader.onloadend = () => {
const base64data = reader.result as string;
if (onCrop) {
onCrop(base64data);
}
};
reader.readAsDataURL(compressedFile);
}
}, 'image/png');
}
};
return (
<div>
<input
type="file"
id="contained-button-file"
onChange={handleFileChange}
accept="image/*"
style={{ display: 'none' }}
/>
<div style={{ display: 'flex', flexWrap: 'wrap', width: '100%', justifyContent: 'center' }}>
{imageSrc && (
<div style={{ margin: '1rem', width: '180px', height: '180px', position: 'relative' }}>
<img ref={imageRef} src={imageSrc} alt="Cropped Image" />
</div>
)}
<div>
{preImgUrl && (
<img
src={preImgUrl}
alt="预览图像"
style={{
width: '80px',
height: '80px',
margin: '1rem',
border: '1px solid #6666',
borderRadius: '50%',
}}
/>
)}
<br />
<label htmlFor="contained-button-file">
<Button
variant="contained"
size="small"
style={{ margin: '0.5rem 1rem' }}
color="primary"
component="span"
>
上传图片
</Button>
</label>
{preImgUrl && (
<div>
{rotateBut && (
<Button
variant="contained"
size="small"
color="primary"
component="span"
style={{ margin: '0.5rem 1rem' }}
onClick={handleRotate}
disabled={!imageSrc}
>
旋转图片
</Button>
)}
<Button
variant="contained"
size="small"
color="primary"
component="span"
onClick={handleCrop}
style={{ margin: '0.5rem 1rem' }}
disabled={!imageSrc}
>
保存图片
</Button>
</div>
)}
</div>
</div>
<div style={{ textAlign: 'center', fontSize: '0.8rem', color: '#999' }}> 上传logo</div>
</div>
);
};
export default ImageCropper;
使用
const handleCrop = (blob: string) => {
console.log(blob);//获取图片
};
<ImageCropper onCrop={handleCrop} avatarImage={img}/>