Canvas动态箭头绘制的核心是用atan2计算方向角并三角函数定位翼点:先算dx、dy,再Math.atan2(dy,dx)得弧度角;单位方向向量ux=dx/len、uy=dy/len,垂直向量vx=-uy、vy=ux;根部点cx=x2-uxheadLen、cy=y2-uyheadLen;左右翼点用tan(angle)偏移;或用save/translate/rotate简化绘制;需防len≈0导致NaN。

Canvas 绘制动态箭头的核心,是让箭头实时对准目标方向——这不靠“猜”或“试”,而是用 起点与终点坐标算出方向角,再用 三角函数定位箭头两翼点。关键不在画多酷,而在每次鼠标移动、节点拖拽或数据更新时,箭头都能数学上精准朝向。
用 atan2 算真实方向角
给定起点 (x1, y1) 和终点 (x2, y2),先算坐标差:
- dx = x2 - x1
- dy = y2 - y1
然后调用 Math.atan2(dy, dx) 得到从起点指向终点的弧度角(注意:Canvas 坐标系 y 向下为正,atan2 天然适配)。这个值直接用于 rotate(),无需手动转象限或加减 π。
箭头两翼点坐标的通用解法
设箭头长度为 headLen(如 12),张角一半为 angle(如 0.4 弧度):
- 先求单位方向向量:ux = dx / len, uy = dy / len(len = Math.hypot(dx, dy),len ≈ 0 时跳过)
- 再求单位垂直向量:vx = -uy, vy = ux(左旋90°)
- 箭头根部中心点:cx = x2 - ux * headLen, cy = y2 - uy * headLen
- 左侧翼点:lx = cx + vx * headLen * Math.tan(angle), ly = cy + vy * headLen * Math.tan(angle)
- 右侧翼点:rx = cx - vx * headLen * Math.tan(angle), ry = cy - vy * headLen * Math.tan(angle)
这样算出的三点(终点、左翼、右翼)构成的三角形,永远正对线段方向,且随线段缩放自然适配。
旋转绘制比逐点计算更轻量
若箭头形状固定(比如等腰三角形),推荐用 save → translate → rotate → draw → restore 流程:
- ctx.save()
- ctx.translate(x2, y2) // 把原点移到箭头尖端
- ctx.rotate(angleRad) // angleRad 就是上面算出的 atan2(dy, dx)
- ctx.beginPath(); ctx.moveTo(0, 0); ctx.lineTo(-headLen, -headLen * Math.tan(angle)); ctx.lineTo(-headLen, headLen * Math.tan(angle)); ctx.closePath(); ctx.fill()
- ctx.restore()
这种方式避免重复计算坐标,尤其适合批量箭头或高频重绘场景(如实时连线图)。
常见坑与应对
实际写代码时容易忽略的细节:
- 极短线段(dx、dy 接近 0)会导致除零或 NaN,必须加 if (len 防御
- Canvas rotate 是绕当前原点旋转,不是绕图形中心——所以要先 translate 到目标点再 rotate
- 角度单位必须统一:Math.sin/cos/atan2 全部用弧度;若输入是角度,记得 × Math.PI / 180
- fill() 比 stroke() 更稳:三角形箭头用 fill 不易出现描边错位或空隙


















