You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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();
  }

}

期望效果:标尺始终与对象的位置、尺寸保持匹配,取消选择后重新选中也能正常显示。


问题分析

核心问题在于:

  1. 直接使用对象的left/top计算标尺位置,旋转后对象的原点位置与实际包围盒位置不匹配,导致偏移错误;
  2. showRuler仅在标尺不存在时创建,重新选中时不会更新已有标尺的属性;
  3. 未实现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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 13:15:00