Fabric.js 自定义控制点:复制、删除、旋转一套搞定
选中的对象周围那 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]);
}
}比如常见场景是"对象锁定后只留移动,不显示旋转和缩放"。选中对象时统一设置一套默认可见性,进入特定工具时再覆盖。
五、坑与经验
- 坐标方向搞反 :
x/y的正负方向一旦写错,控制点会跑偏到对象外部很远。记住x向右、y向下(+y是下)。如果要放右下角,用(0.5, 0.5)。 - 控制点不响应点击 :多半是
mouseUpHandler返回undefined或false,Fabric 就认为没有消费这次事件。控制器方法要返回true。 - 图标不显示 :
document.getElementById取不到 DOM,或图标在对象旋转时方向不对。用render内部ctx.save()/restore()保证状态隔离。 - 删除后残留选中态 :删除时先
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()隔离状态。






