说明
是在react-image-crop
的基础上封装的功能组件。可以获取剪裁后的图片的blob
对象,方便日后拓展使用。
npm i react-image-crop --save
完整代码
import { blobToBlobURL, blobToDataURL } from '@/utils/data';
import { Modal } from 'antd';
import { t } from 'i18next';
import ReactCrop, {
Crop,
PixelCrop,
centerCrop,
makeAspectCrop,
} from 'react-image-crop';
import 'react-image-crop/dist/ReactCrop.css';
const UploadAvatar: React.FC = () => {
const [isModalOpen, setIsModalOpen] = useState(false);
const [imgFile, setImgFile] = useState<File>();
const [imgSrc, setImgSrc] = useState<string>('');
const [crop, setCrop] = useState<Crop>();
const [completedCrop, setCompletedCrop] = useState<PixelCrop>();
const inputRef = useRef<HTMLInputElement>(null);
const imgRef = useRef<HTMLImageElement>(null);
const showModal = () => {
setIsModalOpen(true);
};
const handleOk = () => {
getCroppedImageBlob().then((blob) => {
console.info('>>> blob: ', blob);
console.info('>>> blobURL: ', blobToBlobURL(blob));
setIsModalOpen(false);
});
};
const handleCancel = () => {
setIsModalOpen(false);
};
const handleBtnClick = () => {
if (inputRef.current) {
inputRef.current.click();
}
};
const handleFileSelect = (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0];
if (file) {
console.info('>>> file: ', file);
setImgFile(file);
const reader = new FileReader();
reader.onload = function (event) {
const dataURL = event.target?.result;
setImgSrc(dataURL as string);
setCrop(undefined);
showModal();
};
reader.readAsDataURL(file);
}
};
function centerAspectCrop(
mediaWidth: number,
mediaHeight: number,
aspect: number,
) {
return centerCrop(
makeAspectCrop(
{
unit: '%',
width: 90,
},
aspect,
mediaWidth,
mediaHeight,
),
mediaWidth,
mediaHeight,
);
}
function onImageLoad(e: React.SyntheticEvent<HTMLImageElement>) {
const { width, height } = e.currentTarget;
setCrop(centerAspectCrop(width, height, 1));
}
// 获取剪裁后的图片的 blob 对象
async function getCroppedImageBlob() {
const image = imgRef.current;
if (!image || !completedCrop) {
throw new Error('Image or crop data is missing');
}
// 计算裁剪后图片的尺寸
const scaleX = image.naturalWidth / image.width;
const scaleY = image.naturalHeight / image.height;
const croppedWidth = completedCrop.width * scaleX;
const croppedHeight = completedCrop.height * scaleY;
// 创建 OffscreenCanvas 对象
const offscreenCanvas = new OffscreenCanvas(croppedWidth, croppedHeight);
const ctx = offscreenCanvas.getContext('2d');
if (!ctx) {
throw new Error('No 2D context available');
}
// 在 OffscreenCanvas 上绘制裁剪后的图片
ctx.drawImage(
image,
completedCrop.x * scaleX,
completedCrop.y * scaleY,
croppedWidth,
croppedHeight,
0,
0,
croppedWidth,
croppedHeight,
);
// 将 OffscreenCanvas 转换为 Blob 对象
const blob = await offscreenCanvas.convertToBlob({ type: 'image/png' });
return blob;
}
return (
<div>
<Modal
title={t('upload-avatar')}
open={isModalOpen}
onOk={handleOk}
onCancel={handleCancel}
destroyOnClose
>
<div className="overflow-hidden text-center py-2">
{imgSrc && (
<ReactCrop
crop={crop}
aspect={1}
minHeight={64}
onComplete={(c) => setCompletedCrop(c)}
onChange={(c) => setCrop(c)}
>
<img
ref={imgRef}
className="max-h-[360px]"
src={imgSrc}
onLoad={onImageLoad}
/>
</ReactCrop>
)}
</div>
</Modal>
<Button onClick={handleBtnClick}>Upload</Button>
{/* 隐藏的文件选择按钮 */}
<input
ref={inputRef}
type="file"
accept=".jpg, .jpeg, .png"
style={{ display: 'none' }}
// 在选择文件后触发的事件处理函数
onChange={handleFileSelect}
// 设置多选
multiple={false}
/>
</div>
);
};
export default UploadAvatar;
Comments NOTHING