Fabric.js自定义包围盒:旋转正常但取消重选后位置偏移问题
问题描述
使用Fabric.js开发时,自定义对象包围盒(标尺)遇到以下问题:
- 最初用线条做标尺,旋转时无法与对象同步;
- 改用矩形做包围盒后,旋转同步正常,但取消对象选择后重新选中(尤其是对象已旋转时),标尺的尺寸和位置出现偏差,无法匹配对象。
问题表现:旋转前标尺与对象匹配正常,旋转对象后取消选择再重新选中,标尺位置和尺寸偏移。
相关代码片段:
export class CustomFabricCanvas extends fabric.Canvas { constructor(element, options) { super(element, options); this.rulerLine = null; this.on("selection:created", this.onSelectionCreated.bind(this)); this.on("selection:cleared", this.onSelectionCleared.bind(this)); this.on("object:scaling", this.onObjectScaling.bind(this)); this.on("object:moving", this.onObjectMoving.bind(this)); this.on("object:rotating", this.onObjectRotating.bind(this)); } onSelectionCreated(event) { const activeObject = event.selected; if (activeObject?.length > 0) { activeObject.forEach((obj) => { this.showRuler({ ...obj }); }); } } onSelectionCleared() { this.hideRuler(); } showRuler(options) { if (!this.rulerLine) { this.rulerLine = new fabric.Rect({ left: options.left + 50, top: options.top + 50, height: options.height + 10, width: options.width + 10, stroke: "black", strokeWidth: 1, fill: "transparent", selectable: false, originX: "center", originY: "center", angle: options.angle, }); this.add(this.rulerLine); this.renderAll(); } } hideRuler() { if (this.rulerLine) { this.remove(this.rulerLine); this.rulerLine = null; this.renderAll(); } } onObjectRotating(e) { const target = e.target; console.log(e.target); this.rulerLine.set({ angle: target.angle, }); this.renderAll(); } }
期望效果:标尺始终与对象的位置、尺寸保持匹配,取消选择后重新选中也能正常显示。
问题分析
核心问题在于:
- 直接使用对象的
left/top计算标尺位置,旋转后对象的原点位置与实际包围盒位置不匹配,导致偏移错误; showRuler仅在标尺不存在时创建,重新选中时不会更新已有标尺的属性;- 未实现
object:scaling和object:moving的回调逻辑,对象缩放、移动时标尺无法同步。
解决方案
修正思路:
- 利用对象的
getBoundingRect()方法获取旋转后的实际边界框,以此计算标尺的位置和尺寸; - 重新选中对象时,无论标尺是否存在都更新其属性;
- 统一旋转、缩放、移动的同步逻辑,确保标尺全程匹配对象。
修正后的代码
export class CustomFabricCanvas extends fabric.Canvas { constructor(element, options) { super(element, options); this.rulerLine = null; this.on("selection:created", this.onSelectionCreated.bind(this)); this.on("selection:cleared", this.onSelectionCleared.bind(this)); this.on("object:scaling", this.syncRulerWithObject.bind(this)); this.on("object:moving", this.syncRulerWithObject.bind(this)); this.on("object:rotating", this.syncRulerWithObject.bind(this)); } onSelectionCreated(event) { const activeObject = event.selected[0]; // 单选场景,多选可调整逻辑 if (activeObject) { this.showRuler(activeObject); } } onSelectionCleared() { this.hideRuler(); } showRuler(obj) { const bbox = obj.getBoundingRect(); // 获取对象旋转后的实际边界框 const padding = 5; // 标尺与对象的间距,可自定义 if (!this.rulerLine) { this.rulerLine = new fabric.Rect({ stroke: "black", strokeWidth: 1, fill: "transparent", selectable: false, evented: false, // 避免干扰对象选择交互 }); this.add(this.rulerLine); } // 动态更新标尺的位置、尺寸和角度 this.rulerLine.set({ left: bbox.left - padding, top: bbox.top - padding, width: bbox.width + padding * 2, height: bbox.height + padding * 2, angle: obj.angle, originX: "left", originY: "top", }); this.renderAll(); } hideRuler() { if (this.rulerLine) { this.remove(this.rulerLine); this.rulerLine = null; this.renderAll(); } } syncRulerWithObject(e) { const target = e.target; if (this.rulerLine && target === this.getActiveObject()) { this.showRuler(target); // 复用showRuler逻辑同步标尺 } } }
关键改动说明
getBoundingRect()的使用:该方法返回对象旋转、缩放后的实际包围盒坐标(left/top为包围盒左上角,width/height为实际宽高),确保标尺位置尺寸完全匹配对象;- 动态更新标尺属性:
showRuler不再仅创建一次,每次选中或对象变化时都会更新所有属性,解决重新选中的偏差问题; - 统一同步逻辑:将旋转、缩放、移动的同步逻辑合并到
syncRulerWithObject,复用showRuler逻辑,避免代码重复; evented: false设置:防止标尺干扰对象的选择和交互操作。
内容的提问来源于stack exchange,提问作者azr-arch
相关产品推荐
相关产品推荐

