Fabric.js 旋转磁吸与归正:让对象在松手时自动吸附直角

2026-09-23 56 浏览 0 评论 0 字

做设计/标注类画布,用户旋转一个形状后,往往希望 松手那一瞬间如果能接近横平竖直就自动吸过去。不加这个吸附,对齐全靠手抖;加了之后,产品质感立刻不一样。这篇文章讲讲怎么用极简的数学实现这种"磁吸"。

一、监听旋转事件

Fabric.js 只要监听 object:rotating 。这个事件在对象每一次旋转带动画/拖动过程中高频触发,正好适合做吸附判断:

canvas.on('object:rotating', (e) => {
  snapToRightAngle(e.target);
});

二、取模判断 距哪边最近

核心思路:任意角度 angle ,对它取 90 的模,剩下的就是"离某个直角方向有多远"。如果余数落在容差内,就把角度吸附到最近的整数倍 90°:

function snapToRightAngle(obj, deviationDeg = 5) {
  const bisection = 90;
  const angle = ((obj.angle % 360) + 360) % 360; // 归一化到 0~360
  const remainder = angle % bisection;

  let target = -1;
  if (remainder < deviationDeg) {
    target = Math.floor(angle / bisection) * bisection;   // 前一个直角
  } else if (bisection - remainder < deviationDeg) {
    target = (Math.floor(angle / bisection) + 1) * bisection; // 下一个直角
  }
  if (target !== -1) {
    obj.rotate(target);
    obj.canvas.requestRenderAll();
  }
}

看这个逻辑:角度对 90 同余, remainder 表示 这个角度离上一个直角偏了多少。如果 remainder < 5° ,就往回吸附到上一个直角;如果离下一个直角不足 5°(即 90 - remainder < 5 ),就往前吸附到下一个直角。中间范围(>= 5 且 <= 85)不动,尊重用户的自由旋转。

三、负数角度与多圈的处理

旋转会超过 360 或出现负角度,例如用户逆向转了 800°。直接 % 会得到负数,导致 remainder 计算错乱。所以先把角度归一化到 0~360 :

const angle = ((obj.angle % 360) + 360) % 360;

二是在吸附时保留原来的"圈数"(大小),只在就近方向上做最小改动,避免用户明明转了 3 圈却被突然"拉回"到 0°。上面代码里的是"吸附到就近直角方向",已经天然避免整圈回跳。

四、磁吸强度与手感

容差 deviationDeg 是个重要参数:

  • 太小(1°~2°):几乎不吸附,形同虚设;
  • 太大(15°+):用户想转 10° 都被硬拉回,反而恼火。
  • 建议文档编辑 5° 左右,颜值类工具可以再小一点。

更进一步可以做成:吸附发生在 停止转动 时而非转动过程中,避免抖动。做法是给旋转回调用 clearTimeout/setTimeout 做节流,只在停止后执行一次吸附:

let snapTimer = null;
function onRotating(e) {
  clearTimeout(snapTimer);
  snapTimer = setTimeout(() => snapToRightAngle(e.target), 120);
}

五、坑与经验

  1. 负角度 bug : -10 % 90 在 JS 里是 -10 ,直接会导致 remainder < 5 判不了真。归一化是必须的。
  2. 写入 canvas 再 apply :吸附后 obj.rotate() 可能没有立刻反映到矩形几何上,要 requestRenderAll 刷新,或检测到变化主动 renderAll() 。
  3. 与用户自由旋转的矛盾 :磁吸只应在"拖动旋转"时生效,如果程序主动 obj.set({angle}) (比如镜像后),不要误触发把角度 冲掉。可用标记位区分来源。

六、可验证 demo

在画布放一个矩形,绑定 object:rotating ,手动转它以 5° 为断电。当接近 0/90/180/270 时应对齐到最近的直角;转到 30° 时应保持 30°。两组测试都通过,归正功能即完成。

FAQ 常见问题

  • 问: -10 % 90 在 JS 里算出来不对,吸附判断老失效?
    因为 JS 的取模结果带符号, -10 % 90 得到 -10 , remainder < 5 就判不了真。做磁吸前一定先把角度归一化( ((angle % 90) + 90) % 90 ),负数角度和小数才不会误判。

  • 问:吸附后已经 obj.rotate(...) ,为什么画的矩形没跟着转?
    直接改角度有时不会立刻刷新到几何上。调用后要 canvas.requestRenderAll() ,或检测到角度变化主动 renderAll() ,否则画面上还是旧角度。

  • 问:程序自己 set({angle}) 时会误触磁吸吗?
    会。磁吸只应在"用户拖动旋转"时生效,程序主动改角度(比如镜像后归正)要用一个标记位区分来源,避免把想要的任意角度又给冲回直角。


发布评论

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

评论列表 0

暂无评论