Fabric.js 自定义控制点:复制、删除、旋转一套搞定

2026-09-17 88 浏览 0 评论 0 字

选中的对象周围那 8 个小方块加上上方的旋转柄,Fabric.js 默认样式中规中矩。但在真实产品里,我们往往要的是一套 品牌化 + 自定义功能 的控制层:复制、删除、替换,甚至一键加箭头。这篇文章记录我怎么用 fabric.Control 接管整套控制点。

一、控制点的本质

Fabric 里,每个对象能拖拽的"把手"就是一个 fabric.Control ,它们被放在某个位置(由 x/y 决定),并且可以绑定自定义的 mouseDownHandler 、 mouseUpHandler ,以及决定"拖完怎么变"的 actionHandler 。

默认控制点名字: mt/mb/ml/mr (上中下左右缩放)、 tl/tr/bl/br (四角缩放)、 mtr (旋转)。

二、造一个自定义控制点

比如我要在右上角放一个"删除"把手,右下角放一个"复制"把手:

const deleteControl = new fabric.Control({
  x: 0.5,   // 相对对象中心,x 偏移到右下
  y: -0.5,  // y 偏移到右上
  cursorStyle: 'pointer',
  mouseDownHandler: () => { window.ctlDown = true; },
  mouseUpHandler: () => {
    // 拿到当前选中的对象,执行删除
    removeActiveObject();
  },
});

x/y 取 -0.5 ~ 0.5 表示相对对象宽高比例, (0.5, -0.5) 就是右下角、 (0.5, 0.5) 就是右下角。

三、让控制点显示成图标而不是方块

默认控制点是实心小圆/方块。要换成图标,就要重写它的 render 函数:

function renderIcon(icon) {
  return function (ctx, left, top, styleOverride, fabricObject) {
    ctx.save();
    ctx.translate(left, top);
    ctx.rotate(fabric.util.degreesToRadians(fabricObject.angle));
    ctx.imageSmoothingQuality = 'high';
    ctx.drawImage(icon, -this.cornerSize / 2, -this.cornerSize / 2, this.cornerSize, this.cornerSize);
    ctx.restore();
  };
}

fabric.Object.prototype.controls['deleteControl'] = new fabric.Control({
  x: -0.5, y: -0.5,
  cursorStyle: 'pointer',
  mouseUpHandler: removeItem,
  render: renderIcon(document.getElementById('deleteIcon')), // 一个 <img> 或 <svg> 引用的 DOM
  cornerSize: 32,
});

注意 render 里的 this.cornerSize 通过闭包取出当前控制点尺寸。 ctx.drawImage 的图标来源可以是页面上的 <svg> 或 <img> 的 DOM 元素。用这种方式,图标会在对象旋转时跟着旋转且始终朝向用户——这里我们主动 ctx.rotate(angle) 将其"扶正"。

四、控制点的显隐切换

业务里经常需要"不同工具模式下显示不同控制点"。Fabric 支持控制点级显隐:

function setControlsVisible(obj, config) {
  // config 形如 { tl: false, tr: false, br: true, mtr: false }
  for (const key in config) {
    obj.setControlVisible(key, config[key]);
  }
}

比如常见场景是"对象锁定后只留移动,不显示旋转和缩放"。选中对象时统一设置一套默认可见性,进入特定工具时再覆盖。

五、坑与经验

  1. 坐标方向搞反 : x/y 的正负方向一旦写错,控制点会跑偏到对象外部很远。记住 x 向右、 y 向下( +y 是下)。如果要放右下角,用 (0.5, 0.5) 。
  2. 控制点不响应点击 :多半是 mouseUpHandler 返回 undefined 或 false ,Fabric 就认为没有消费这次事件。控制器方法要返回 true 。
  3. 图标不显示 : document.getElementById 取不到 DOM,或图标在对象旋转时方向不对。用 render 内部 ctx.save()/restore() 保证状态隔离。
  4. 删除后残留选中态 :删除时先 canvas.discardActiveObject() ,再 canvas.remove(obj) ,最后 requestRenderAll() 。

六、验证方式

写一个最小页面,加一个矩形,注册"删除/复制/旋转"三个自定义控制点,分别测试:点击图标能否触发、对象旋转后图标是否跟随、控制点显隐切换是否生效。核心就在这几个 fabric.Control 的配置上,调通了整套控制层就基本成型。

FAQ 常见问题

  • 问:自定义控制点点了没反应,为什么?
    通常是 mouseUpHandler 没有显式返回 true 。Fabric 靠返回值判断事件是否被消费,返回 undefined 或 false 就会当作没处理。

  • 问:控制点位置跑偏甚至跑到对象外面很远?
    多半是 x/y 的正负方向写反了。记住 x 向右、 y 向下,放在对象内部用 -0.5 ~ 0.5 的比例范围。

  • 问:对象旋转后图标朝向不对怎么办?
    在 render 回调里先 ctx.rotate(-对象角度) 把坐标系"扶正",再 drawImage 画图标,并用 ctx.save()/restore() 隔离状态。


发布评论

发布评论前请先 登录。
0 评论
点赞
收藏

评论列表 0

暂无评论