react-image-crop图片裁剪问题:渲染异常与保存时drawImage报错求助
React-Image-Crop 裁剪报错解决方案
问题场景
使用react-image-crop实现图片裁剪时遇到两个问题:
- 直接使用
<ReactCrop src={src} onImageLoaded={setImage} crop={crop} onChange={setCrop} />时,图片无法正常渲染 - 改用嵌套img的写法
<ReactCrop crop={crop} onChange={setCrop}><img src={src} onLoad={setImage} alt="crop" /></ReactCrop>后,图片显示正常且可裁剪,但点击保存裁剪结果时抛出错误:
Uncaught TypeError: Failed to execute 'drawImage' on 'CanvasRenderingContext2D': The provided value is not of type '(CSSImageValue or HTMLCanvasElement or HTMLImageElement or HTMLVideoElement or ImageBitmap or OffscreenCanvas or SVGImageElement or VideoFrame)'
错误原因
img标签的onLoad事件默认传递的是事件对象,而非图片元素本身。原代码中直接将setImage绑定到onLoad,导致image状态存储的是事件对象,而非HTMLImageElement实例,后续调用drawImage时自然会因参数类型不匹配报错。
修复方案
修改img的onLoad回调,手动将图片元素传递给setImage:
// 原写法 <img src={src} onLoad={setImage} alt="crop" /> // 修改后 <img src={src} onLoad={(e) => setImage(e.target)} alt="crop" />
额外优化点:
- 裁剪前增加参数校验,避免空值触发报错
- 新增
useEffect清理createObjectURL创建的对象URL,防止内存泄漏
修改后的完整代码
import { useState, useEffect } from 'react'; import ReactCrop from 'react-image-crop'; import 'react-image-crop/dist/ReactCrop.css'; function ImageCropper() { const [src, setSrc] = useState(null); const [crop, setCrop] = useState({ aspect: 16 / 9 }); const [image, setImage] = useState(null); const [output, setOutput] = useState(null); // 清理对象URL,防止内存泄漏 useEffect(() => { return () => { if (src) { URL.revokeObjectURL(src); } }; }, [src]); const selectImage = (file) => { if (file) { setSrc(URL.createObjectURL(file)); } }; const cropImageNow = () => { // 校验必要参数,避免空值报错 if (!image || !crop.width || !crop.height) return; const canvas = document.createElement('canvas'); const scaleX = image.naturalWidth / image.width; const scaleY = image.naturalHeight / image.height; const pixelRatio = window.devicePixelRatio; canvas.width = crop.width * pixelRatio; canvas.height = crop.height * pixelRatio; const ctx = canvas.getContext('2d'); ctx.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0); ctx.imageSmoothingQuality = 'high'; ctx.drawImage( image, crop.x * scaleX, crop.y * scaleY, crop.width * scaleX, crop.height * scaleY, 0, 0, crop.width, crop.height, ); const base64Image = canvas.toDataURL('image/jpeg'); setOutput(base64Image); }; return ( <div className="App"> <center> <input type="file" accept="image/*" onChange={(e) => { selectImage(e.target.files[0]); }} /> <br /> <br /> <div> {src && ( <div> <ReactCrop crop={crop} onChange={setCrop}> {/* 修改onLoad回调,传递图片元素 */} <img src={src} onLoad={(e) => setImage(e.target)} alt="crop" /> </ReactCrop> <br /> <button onClick={cropImageNow}>Crop</button> <br /> <br /> </div> )} </div> <div>{output && <img src={output} />}</div> </center> </div> ); } export default ImageCropper;
内容的提问来源于stack exchange,提问作者Asmar Munir
相关产品推荐
相关产品推荐

