React JS上传图片前后如何按固定比例裁剪?react-easy-crop实现求助
React 头像裁剪上传实现方案(基于rsuite Uploader + react-easy-crop)
前置准备
先安装裁剪库依赖:
npm install react-easy-crop # 或 yarn add react-easy-crop
核心实现逻辑
调整原有上传逻辑:选中图片后不直接上传,先弹出裁剪框按固定比例裁剪,确认后再上传裁剪后的图片。需要补充裁剪区域坐标计算、裁剪后图片生成两个核心逻辑。
完整修改后代码
import React from "react"; import { Uploader, Icon, Alert, Loader, ControlLabel, Modal, Button } from "rsuite"; import Cropper from "react-easy-crop"; import localStorage from "../../utils/lib/local-storage"; const dataUser = localStorage().get("auth"); // 工具函数:将file转base64 const previewFile = (file, callback) => { const reader = new FileReader(); reader.onloadend = () => { callback(reader.result); }; reader.readAsDataURL(file); }; // 工具函数:根据裁剪区域生成裁剪后的blob文件 const getCroppedImg = (imageSrc, pixelCrop) => { return new Promise((resolve, reject) => { const image = new Image(); image.src = imageSrc; image.onload = () => { const canvas = document.createElement("canvas"); const ctx = canvas.getContext("2d"); canvas.width = pixelCrop.width; canvas.height = pixelCrop.height; ctx.drawImage( image, pixelCrop.x, pixelCrop.y, pixelCrop.width, pixelCrop.height, 0, 0, pixelCrop.width, pixelCrop.height ); canvas.toBlob((blob) => { if (blob) { resolve(blob); } else { reject(new Error("裁剪图片生成失败")); } }, "image/jpeg"); }; image.onerror = reject; }); }; const Avatar = ({ image = false, styles = { width: 120, height: 120, margin: 0, borderRadius: "50%", border: "1px solid #ddd" }, label = false, urlUpload = "/users/set-avatar" }) => { const [uploading, setUploading] = React.useState(false); const [fileInfo, setFileInfo] = React.useState(image); // 裁剪相关状态 const [cropModalVisible, setCropModalVisible] = React.useState(false); const [originalImage, setOriginalImage] = React.useState(""); const [crop, setCrop] = React.useState({ x: 0, y: 0 }); const [zoom, setZoom] = React.useState(1); const [croppedAreaPixels, setCroppedAreaPixels] = React.useState(null); // 确认裁剪并上传 const handleCropConfirm = async () => { try { setUploading(true); setCropModalVisible(false); // 生成裁剪后的blob const croppedBlob = await getCroppedImg(originalImage, croppedAreaPixels); // 构造FormData手动上传 const formData = new FormData(); formData.append("images", croppedBlob, "avatar.jpg"); // 调用上传接口 const res = await fetch(`${process.env.REACT_APP_BASE_URL}${urlUpload}`, { method: "POST", headers: { Authorization: "Bearer " + dataUser.token }, body: formData }); if (res.ok) { // 上传成功更新预览 previewFile(croppedBlob, value => { setFileInfo(value); }); Alert.success("上传成功"); window.location.reload(); } else { throw new Error("上传失败"); } } catch (e) { setFileInfo(null); Alert.error("操作失败,请重试"); } finally { setUploading(false); } }; return ( <div style={{ marginBottom: label ? 15 : 0 }}> {label && <ControlLabel>{label}</ControlLabel>} <Uploader fileListVisible={false} listType="picture" autoUpload={false} // 关闭自动上传,走自定义裁剪逻辑 onChange={(file) => { // 选中文件后先转base64打开裁剪框 previewFile(file.blobFile, value => { setOriginalImage(value); setCropModalVisible(true); }); }} name="images" headers={{ Authorization: "Bearer " + dataUser.token }} > <button type="button" style={styles}> {uploading && <Loader backdrop center />} {fileInfo ? ( <img src={fileInfo} alt="avatar" width="100%" height="100%" style={{ borderRadius: "50%" }} /> ) : ( <Icon icon="avatar" size="5x" /> )} </button> </Uploader> {/* 裁剪弹窗 */} <Modal open={cropModalVisible} onClose={() => setCropModalVisible(false)}> <Modal.Header> <Modal.Title>裁剪头像</Modal.Title> </Modal.Header> <Modal.Body> <div style={{ position: "relative", width: "100%", height: 400 }}> <Cropper image={originalImage} crop={crop} zoom={zoom} aspect={1} // 固定1:1裁剪比例,可根据需求调整为其他比例,例如4/3 onCropChange={setCrop} onZoomChange={setZoom} onCropComplete={(_, pixelCrop) => setCroppedAreaPixels(pixelCrop)} /> </div> <div style={{ marginTop: 20 }}> <p>缩放比例: {zoom.toFixed(1)}x</p> <input type="range" min={1} max={3} step={0.1} value={zoom} onChange={(e) => setZoom(Number(e.target.value))} style={{ width: "100%" }} /> </div> </Modal.Body> <Modal.Footer> <Button onClick={() => setCropModalVisible(false)}>取消</Button> <Button onClick={handleCropConfirm} appearance="primary">确认裁剪并上传</Button> </Modal.Footer> </Modal> </div> ); }; export default Avatar;
调整说明
- 代码默认设置1:1的裁剪比例适配头像场景,可修改Cropper组件的
aspect参数自定义宽高比 - 保留了原有组件的所有入参和交互逻辑,原有业务代码无需修改即可直接接入
- 手动处理上传逻辑避免了rsuite默认上传未裁剪原始图的问题
内容的提问来源于stack exchange,提问作者NicoGulo
相关产品推荐
相关产品推荐

