如何实现FabricJS扩展裁剪功能?现有代码存在边界与旋转适配问题
Fabric.js 图片裁剪功能优化方案
针对当前代码存在的裁剪框拖拽/缩放超出图片边界、图片旋转后裁剪功能失效两个问题,优化思路及实现如下:
问题原因
- 原有边界校验仅判断了裁剪框的
top/left坐标,未覆盖裁剪框缩放时四个边角的越界场景 - 未处理图片的
angle旋转属性,所有坐标计算都是基于0度角的正交坐标系,旋转后坐标映射完全失效 - 裁剪框和蒙层未同步继承图片的旋转角度,导致旋转后蒙层和裁剪框与图片位置错位
优化后完整代码
document.getElementById("crop").onclick = function () { const activeImg = canvas.getActiveObject(); if (!activeImg || activeImg.get("type") !== "image") return; // 缓存图片基础属性 const imgAttr = { left: activeImg.left, top: activeImg.top, width: activeImg.getScaledWidth(), height: activeImg.getScaledHeight(), scaleX: activeImg.scaleX, scaleY: activeImg.scaleY, angle: activeImg.angle || 0, originX: activeImg.originX, originY: activeImg.originY, }; // 初始化裁剪框,同步图片旋转角度、变换原点 const cropRect = new fabric.Rect({ id: "crop-rect", top: imgAttr.top, left: imgAttr.left, width: imgAttr.width, height: imgAttr.height, stroke: "rgb(42, 67, 101)", strokeWidth: 2, strokeDashArray: [5, 5], fill: "rgba(255, 255, 255, 1)", globalCompositeOperation: "overlay", angle: imgAttr.angle, originX: imgAttr.originX, originY: imgAttr.originY, transparentCorners: false, }); cropRect.setControlVisible("mtr", false); // 隐藏裁剪框旋转控件 // 初始化蒙层,同步图片旋转角度、变换原点 const overlayRect = new fabric.Rect({ id: "overlay-rect", top: imgAttr.top, left: imgAttr.left, width: imgAttr.width, height: imgAttr.height, selectable: false, evented: false, fill: "rgba(0, 0, 0, 0.5)", angle: imgAttr.angle, originX: imgAttr.originX, originY: imgAttr.originY, }); // 处理已有裁剪属性的场景 if (activeImg.cropX) { const prevCropX = activeImg.cropX; const prevCropY = activeImg.cropY; const prevCropWidth = activeImg.width; const prevCropHeight = activeImg.height; activeImg.set({ cropX: null, cropY: null, left: activeImg.left - prevCropX * activeImg.scaleX, top: activeImg.top - prevCropY * activeImg.scaleY, width: activeImg._originalElement.naturalWidth, height: activeImg._originalElement.naturalHeight, }); activeImg.dirty = true; // 更新裁剪框和蒙层坐标 cropRect.set({ left: activeImg.left + prevCropX * activeImg.scaleX, top: activeImg.top + prevCropY * activeImg.scaleY, width: prevCropWidth * activeImg.scaleX, height: prevCropHeight * activeImg.scaleY, }); cropRect.dirty = true; overlayRect.set({ left: activeImg.left, top: activeImg.top, width: activeImg.width * activeImg.scaleX, height: activeImg.height * activeImg.scaleY, }); overlayRect.dirty = true; } canvas.add(overlayRect); canvas.add(cropRect); canvas.discardActiveObject(); canvas.setActiveObject(cropRect); canvas.renderAll(); // 裁剪框边界校验(自动适配旋转/缩放场景) function restrictCropRect() { const imgBound = activeImg.getBoundingRect(); const cropBound = cropRect.getBoundingRect(); // 左边界校验 if (cropBound.left < imgBound.left) { cropRect.left += imgBound.left - cropBound.left; } // 右边界校验 if (cropBound.left + cropBound.width > imgBound.left + imgBound.width) { cropRect.left -= (cropBound.left + cropBound.width) - (imgBound.left + imgBound.width); } // 上边界校验 if (cropBound.top < imgBound.top) { cropRect.top += imgBound.top - cropBound.top; } // 下边界校验 if (cropBound.top + cropBound.height > imgBound.top + imgBound.height) { cropRect.top -= (cropBound.top + cropBound.height) - (imgBound.top + imgBound.height); } canvas.renderAll(); } // 绑定裁剪框移动、缩放时的边界校验 cropRect.on("scaling", restrictCropRect); cropRect.on("moving", restrictCropRect); // 裁剪框取消选中时执行裁剪 cropRect.on("deselected", applyCrop); // 执行裁剪逻辑 function applyCrop() { const cropLocalLeft = (cropRect.left - activeImg.left) / activeImg.scaleX; const cropLocalTop = (cropRect.top - activeImg.top) / activeImg.scaleY; const cropLocalWidth = (cropRect.width * cropRect.scaleX) / activeImg.scaleX; const cropLocalHeight = (cropRect.height * cropRect.scaleY) / activeImg.scaleY; activeImg.set({ cropX: cropLocalLeft, cropY: cropLocalTop, width: cropLocalWidth, height: cropLocalHeight, left: activeImg.left + cropLocalLeft * activeImg.scaleX, top: activeImg.top + cropLocalTop * activeImg.scaleY, }); canvas.remove(cropRect); canvas.remove(overlayRect); canvas.renderAll(); } };
核心优化说明
- 统一使用
getBoundingRect()获取元素在画布上的实际坐标边界,自动适配旋转、缩放属性,不需要手动计算角度偏移 - 新增裁剪框
scaling事件的边界校验,覆盖拖拽边角缩放时的越界场景 - 裁剪框、蒙层同步继承图片的
angle、originX、originY属性,避免旋转后错位 - 蒙层新增
evented: false属性,避免蒙层拦截画布交互事件
内容的提问来源于stack exchange,提问作者DimiDev
相关产品推荐
相关产品推荐

