【React】前端处理图片的上传、预览、移动、缩放和旋转
文章目录
- 概要
- 核心代码
- 图片上传组件
- 图片移动功能
- 图片缩放功能
- 图片旋转功能
- 技术要点分析
- 完整代码
- 实现效果
- 小结
概要
一个老项目维护新功能,提出了对图片的各种处理,由于某些原因不能使用Antd的Image组件(但是实际上使用了Antd其他组件,客户总会提出奇怪的要求),于是自己实现了一个常见的处理图片功能。
本文将介绍如何实现一个完整的图片上传预览系统,包括上传、预览、移动、缩放和旋转等功能。
我们实现的功能包括:
- 图片上传和预览
- 鼠标拖动移动图片位置
- 滚轮缩放图片大小
- 按钮控制图片旋转
- 滑块控制精确缩放比例。
完整代码跑出来的样子,可以根据自己的需求修改样式和自定义展示逻辑
核心代码
图片上传组件
使用Ant Design的Upload组件实现图片上传功能:
false} onChange=https://blog.csdn.net/Zacks_xdc/article/details/{handleChange} beforeUpload=https://blog.csdn.net/Zacks_xdc/article/details/{(file) = https://blog.csdn.net/Zacks_xdc/article/details/{ const isJpgOrPng = file.type === "image/jpeg" || file.type === "image/png"; if (!isJpgOrPng) https://blog.csdn.net/Zacks_xdc/article/details/{ message.error("只能上传 JPG/PNG 文件!"); } return false; // 返回false阻止自动上传 }} accept="image/jpeg,image/png" > https://blog.csdn.net/Zacks_xdc/article/details/{imgUrl ? ( imgUrl} alt="avatar" style=https://blog.csdn.net/Zacks_xdc/article/details/{https://blog.csdn.net/Zacks_xdc/article/details/{ width: "auto", height: "auto", maxWidth: "100%", maxHeight: 300, objectFit: "contain", }} / ) : (
https://blog.csdn.net/Zacks_xdc/article/details/{ marginTop: 8 }}Upload)}图片移动功能
const handleMouseDown = (e: React.MouseEvent) => https://blog.csdn.net/Zacks_xdc/article/details/{ setIsDragging(true); setDragStartPos(https://blog.csdn.net/Zacks_xdc/article/details/{ x: e.clientX - moveConfig.moveX, y: e.clientY - moveConfig.moveY, }); }; const handleMouseMove = (e: React.MouseEvent) => https://blog.csdn.net/Zacks_xdc/article/details/{ if (!isDragging) return; setMoveConfig(https://blog.csdn.net/Zacks_xdc/article/details/{ moveX: e.clientX - dragStartPos.x, moveY: e.clientY - dragStartPos.y, }); }; const handleMouseUp = () => https://blog.csdn.net/Zacks_xdc/article/details/{ setIsDragging(false); };
图片缩放功能
useEffect(() => https://blog.csdn.net/Zacks_xdc/article/details/{ if (imgDivRef.current) https://blog.csdn.net/Zacks_xdc/article/details/{ const handleWheel = (e: WheelEvent) => https://blog.csdn.net/Zacks_xdc/article/details/{ e.preventDefault(); e.stopImmediatePropagation(); const delta = e.deltaY > 0 ? -0.1 : 0.1; setScale((prev) => Math.max(0.1, Math.min(3, prev + delta))); }; imgDivRef.current.addEventListener("wheel", handleWheel, https://blog.csdn.net/Zacks_xdc/article/details/{ passive: false, }); return () => https://blog.csdn.net/Zacks_xdc/article/details/{ imgDivRef.current?.removeEventListener("wheel", handleWheel); }; } }, [imgUrl]);
图片旋转功能
const handleRotate = (angle: number) => https://blog.csdn.net/Zacks_xdc/article/details/{ setRotate((prevRotate) => prevRotate + angle); }; // 在渲染中使用 () = handleRotate(-90)} /> () = handleRotate(90)} />
技术要点分析
- 事件处理优化 :
- 使用 passive: false 确保能阻止默认滚动行为
- 通过 stopImmediatePropagation 阻止事件冒泡
- 在useEffect中正确添加和移除事件监听器
- 性能考虑 :
- 使用transform代替直接修改位置属性,利用GPU加速
- 限制缩放范围(0.1-3倍)防止极端情况
- 用户体验 :
- 用户体验 :
- 拖动时显示grabbing光标提升交互感
- 限制文件类型为JPG/PNG
- 提供多种控制方式(按钮+滑块)
完整代码
import React, https://blog.csdn.net/Zacks_xdc/article/details/{ useState, useRef, useEffect } from "react"; import https://blog.csdn.net/Zacks_xdc/article/details/{ Upload, Button, Slider, message, Space } from "antd"; import https://blog.csdn.net/Zacks_xdc/article/details/{ PlusOutlined, RotateLeftOutlined, RotateRightOutlined, ZoomInOutlined, ZoomOutOutlined, } from "@ant-design/icons"; import type https://blog.csdn.net/Zacks_xdc/article/details/{ RcFile, UploadProps } from "antd/es/upload"; interface MoveConfig https://blog.csdn.net/Zacks_xdc/article/details/{ moveX: number; moveY: number; } const PictureUpload: React.FC = () => https://blog.csdn.net/Zacks_xdc/article/details/{ const [imgUrl, setImgUrl] = useState(""); const [scale, setScale] = useState(1); const [rotate, setRotate] = useState(0); const [moveConfig, setMoveConfig] = useState(https://blog.csdn.net/Zacks_xdc/article/details/{ moveX: 0, moveY: 0, }); const [isDragging, setIsDragging] = useState(false); const [dragStartPos, setDragStartPos] = useState(https://blog.csdn.net/Zacks_xdc/article/details/{ x: 0, y: 0 }); const imgRef = useRef(null); const imgDivRef = useRef(null); const getBase64 = (img: RcFile, callback: (url: string) => void) => https://blog.csdn.net/Zacks_xdc/article/details/{ const reader = new FileReader(); reader.addEventListener("load", () => callback(reader.result as string)); reader.readAsDataURL(img); }; const handleChange: UploadProps["onChange"] = (info) => https://blog.csdn.net/Zacks_xdc/article/details/{ const file = info.file as RcFile; console.log("Uploaded file:", info, file); // 打印上传的文件对象 if (file) https://blog.csdn.net/Zacks_xdc/article/details/{ getBase64(file, (url) => https://blog.csdn.net/Zacks_xdc/article/details/{ setImgUrl(url); }); } }; const handleMouseDown = (e: React.MouseEvent) => https://blog.csdn.net/Zacks_xdc/article/details/{ setIsDragging(true); setDragStartPos(https://blog.csdn.net/Zacks_xdc/article/details/{ x: e.clientX - moveConfig.moveX, y: e.clientY - moveConfig.moveY, }); }; const handleMouseMove = (e: React.MouseEvent) => https://blog.csdn.net/Zacks_xdc/article/details/{ if (!isDragging) return; setMoveConfig(https://blog.csdn.net/Zacks_xdc/article/details/{ moveX: e.clientX - dragStartPos.x, moveY: e.clientY - dragStartPos.y, }); }; const handleMouseUp = () => https://blog.csdn.net/Zacks_xdc/article/details/{ setIsDragging(false); }; const handleRotate = (angle: number) => https://blog.csdn.net/Zacks_xdc/article/details/{ setRotate((prevRotate) => prevRotate + angle); }; const handleScale = (value: number | Function) => https://blog.csdn.net/Zacks_xdc/article/details/{ // 如果直接传入值(来自Slider) if (typeof value === "number") https://blog.csdn.net/Zacks_xdc/article/details/{ setScale(Math.max(0.1, Math.min(3, value))); // 限制在0.1-3范围内 } // 如果来自按钮点击(增量) else if (typeof value === "function") https://blog.csdn.net/Zacks_xdc/article/details/{ setScale((prev) => https://blog.csdn.net/Zacks_xdc/article/details/{ const newScale = value(prev); return Math.max(0.1, Math.min(3, newScale)); // 限制在0.1-3范围内 }); } }; useEffect(() => https://blog.csdn.net/Zacks_xdc/article/details/{ if (imgDivRef.current) https://blog.csdn.net/Zacks_xdc/article/details/{ const handleWheel = (e: WheelEvent) => https://blog.csdn.net/Zacks_xdc/article/details/{ console.log("wheel event:", e); e.preventDefault(); e.stopImmediatePropagation(); const delta = e.deltaY > 0 ? -0.1 : 0.1; setScale((prev) => Math.max(0.1, Math.min(3, prev + delta))); return false; }; // 添加非passive事件监听 imgDivRef.current.addEventListener("wheel", handleWheel, https://blog.csdn.net/Zacks_xdc/article/details/{ passive: false, }); return () => https://blog.csdn.net/Zacks_xdc/article/details/{ imgDivRef.current.removeEventListener("wheel", handleWheel); }; } }, [imgUrl]); // 依赖imgUrl,确保每次imgUrl变化时重新添加事件 // 修改Upload组件配置 return ( https://blog.csdn.net/Zacks_xdc/article/details/{ padding: 24 }} false} onChange=https://blog.csdn.net/Zacks_xdc/article/details/{handleChange} beforeUpload=https://blog.csdn.net/Zacks_xdc/article/details/{(file) = https://blog.csdn.net/Zacks_xdc/article/details/{ const isJpgOrPng = file.type === "image/jpeg" || file.type === "image/png"; if (!isJpgOrPng) https://blog.csdn.net/Zacks_xdc/article/details/{ message.error("只能上传 JPG/PNG 文件!"); } return false; // 返回false阻止自动上传 }} accept="image/jpeg,image/png" > https://blog.csdn.net/Zacks_xdc/article/details/{imgUrl ? ( imgUrl} alt="avatar" style=https://blog.csdn.net/Zacks_xdc/article/details/{https://blog.csdn.net/Zacks_xdc/article/details/{ width: "auto", height: "auto", maxWidth: "100%", maxHeight: 300, objectFit: "contain", }} / ) : (
https://blog.csdn.net/Zacks_xdc/article/details/{ marginTop: 8 }}Upload)} https://blog.csdn.net/Zacks_xdc/article/details/{imgUrl && (https://blog.csdn.net/Zacks_xdc/article/details/{/* 图片编辑区域 */} imgDivRef} style=https://blog.csdn.net/Zacks_xdc/article/details/{https://blog.csdn.net/Zacks_xdc/article/details/{ position: "relative", overflow: "hidden", width: "100%", height: 660, border: "1px dashed #ccc", marginTop: 16, }} onMouseDown=https://blog.csdn.net/Zacks_xdc/article/details/{handleMouseDown} onMouseMove=https://blog.csdn.net/Zacks_xdc/article/details/{handleMouseMove} onMouseUp=https://blog.csdn.net/Zacks_xdc/article/details/{handleMouseUp} onMouseLeave=https://blog.csdn.net/Zacks_xdc/article/details/{handleMouseUp} imgRef} src=https://blog.csdn.net/Zacks_xdc/article/details/{imgUrl} style=https://blog.csdn.net/Zacks_xdc/article/details/{https://blog.csdn.net/Zacks_xdc/article/details/{ position: "absolute", top: "50%", left: "50%", transform: `translate(calc(-50% + $https://blog.csdn.net/Zacks_xdc/article/details/{moveConfig.moveX}px), calc(-50% + $https://blog.csdn.net/Zacks_xdc/article/details/{moveConfig.moveY}px)) scale($https://blog.csdn.net/Zacks_xdc/article/details/{scale}) rotate($https://blog.csdn.net/Zacks_xdc/article/details/{rotate}deg)`, cursor: isDragging ? "grabbing" : "grab", transformOrigin: "center center", maxWidth: "none", maxHeight: "none", width: "auto", height: "auto", }} draggable=https://blog.csdn.net/Zacks_xdc/article/details/{false} onLoad=https://blog.csdn.net/Zacks_xdc/article/details/{(e) = https://blog.csdn.net/Zacks_xdc/article/details/{ const img = e.target as HTMLImageElement; if (imgRef.current) https://blog.csdn.net/Zacks_xdc/article/details/{ imgRef.current.width = img.naturalWidth; imgRef.current.height = img.naturalHeight; } }} />https://blog.csdn.net/Zacks_xdc/article/details/{/* 控制按钮区域 */} https://blog.csdn.net/Zacks_xdc/article/details/{ marginTop: 16 }} () = handleRotate(-90)} /> () = handleRotate(90)} /> () = handleScale(scale + 0.1)} /> () = handleScale(scale - 0.1)} /> https://blog.csdn.net/Zacks_xdc/article/details/{/* 缩放控制 */} https://blog.csdn.net/Zacks_xdc/article/details/{ marginTop: 16 }}缩放: https://blog.csdn.net/Zacks_xdc/article/details/{scale.toFixed(1)}x
0.1} max=https://blog.csdn.net/Zacks_xdc/article/details/{3} step=https://blog.csdn.net/Zacks_xdc/article/details/{0.1} value=https://blog.csdn.net/Zacks_xdc/article/details/{scale} onChange=https://blog.csdn.net/Zacks_xdc/article/details/{handleScale} / )} ); }; export default PictureUpload;实现效果
最终实现的图片编辑器具有以下特点:
- 简洁的上传界面
- 直观的拖拽移动体验
- 平滑的滚轮缩放效果
- 精确的旋转控制
- 实时的缩放比例显示
小结
这个实现可以轻松集成到各种Web应用中,为用户提供完善的图片编辑体验。通过进一步扩展,还可以添加更多功能如裁剪、滤镜等,打造更强大的图片编辑器。
- 事件处理优化 :
免责声明:我们致力于保护作者版权,注重分享,被刊用文章因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理! 部分文章是来自自研大数据AI进行生成,内容摘自(百度百科,百度知道,头条百科,中国民法典,刑法,牛津词典,新华词典,汉语词典,国家院校,科普平台)等数据,内容仅供学习参考,不准确地方联系删除处理! 图片声明:本站部分配图来自人工智能系统AI生成,觅知网授权图片,PxHere摄影无版权图库和百度,360,搜狗等多加搜索引擎自动关键词搜索配图,如有侵权的图片,请第一时间联系我们。