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

如何在FabricJS中通过拖拽鼠标在图形间绘制连接线?

当然可行啦!在FabricJS里实现拖拽鼠标连接两个组件的功能是完全没问题的,我给你梳理几个核心的实现思路,结合你的Angular场景来拆解:

核心实现思路

1. 监听鼠标事件,追踪拖拽的起始与结束

你需要给Fabric Canvas绑定三个关键鼠标事件,来完成从“触发绘制”到“实时预览”再到“完成连接”的完整流程:

  • mouse:down:当鼠标在状态节点上按下时,记录起始节点和当前鼠标位置,同时创建一条临时的预览连接线,这条线会跟着鼠标移动。
  • mouse:move:如果处于拖拽绘制状态,实时更新临时预览线的终点坐标,让用户看到绘制轨迹。
  • mouse:up:松开鼠标时,检查当前位置是否落在另一个状态节点上,如果是,就用正式线条替换临时线;如果不是,就删除临时线,取消这次绘制。

结合你的Angular组件,代码示例如下:

// 假设你已经在组件中初始化了fabric.Canvas实例,存在this.canvas中
private startNode: fabric.Object | null = null;
private tempLine: fabric.Line | null = null;

// 在组件初始化或canvas创建完成后绑定事件
ngAfterViewInit() {
  this.bindCanvasEvents();
}

private bindCanvasEvents() {
  // 鼠标按下,触发绘制起始
  this.canvas.on('mouse:down', (opt) => {
    const target = opt.target;
    // 判断点击的是你的状态节点(这里假设节点有自定义标识isStateNode)
    if (target && target.type === 'rect' && (target as any).isStateNode) {
      this.startNode = target;
      const pointer = this.canvas.getPointer(opt.e);
      // 创建临时预览线,起点为节点中心
      this.tempLine = new fabric.Line(
        [
          target.left + target.width/2,
          target.top + target.height/2,
          pointer.x,
          pointer.y
        ],
        { stroke: '#ccc', strokeWidth: 2, selectable: false, evented: false }
      );
      this.canvas.add(this.tempLine);
    }
  });

  // 鼠标移动,更新预览线
  this.canvas.on('mouse:move', (opt) => {
    if (this.tempLine && this.startNode) {
      const pointer = this.canvas.getPointer(opt.e);
      this.tempLine.set({ x2: pointer.x, y2: pointer.y });
      this.canvas.renderAll();
    }
  });

  // 鼠标松开,完成或取消绘制
  this.canvas.on('mouse:up', (opt) => {
    const target = opt.target;
    // 检查是否点击到了另一个状态节点
    if (this.tempLine && target && target !== this.startNode && (target as any).isStateNode) {
      // 创建正式连接线,连接两个节点的中心
      const finalLine = new fabric.Line(
        [
          this.startNode.left + this.startNode.width/2,
          this.startNode.top + this.startNode.height/2,
          target.left + target.width/2,
          target.top + target.height/2
        ],
        { stroke: '#333', strokeWidth: 2 }
      );
      // 给线条添加关联属性,方便后续管理(比如节点移动时更新线条)
      (finalLine as any).fromNode = this.startNode;
      (finalLine as any).toNode = target;
      this.canvas.add(finalLine);
    }
    // 清理临时状态
    if (this.tempLine) {
      this.canvas.remove(this.tempLine);
      this.tempLine = null;
      this.startNode = null;
    }
  });
}

2. 优化节点吸附体验(可选)

为了让用户更容易对准节点,你可以在mouse:up时添加范围检测:如果鼠标位置距离某个节点的中心在设定阈值内(比如20px),就自动将线条连接到该节点,不用严格点击到节点上。

示例代码片段:

this.canvas.on('mouse:up', (opt) => {
  const pointer = this.canvas.getPointer(opt.e);
  // 遍历所有节点,寻找距离最近的目标节点
  const nearestNode = this.canvas.getObjects().find(obj => {
    if (!(obj as any).isStateNode) return false;
    const centerX = obj.left + obj.width/2;
    const centerY = obj.top + obj.height/2;
    const distance = Math.sqrt(Math.pow(pointer.x - centerX, 2) + Math.pow(pointer.y - centerY, 2));
    return distance <= 20; // 20px的吸附阈值
  });

  if (this.tempLine && nearestNode && nearestNode !== this.startNode) {
    // 创建正式连接线到最近节点的中心
    // ... 同上面的正式线条创建逻辑
  }
  // 清理临时状态
});

3. 动态更新连接线(可选)

如果你的状态节点支持拖拽移动,那连接线也应该跟着节点同步更新。可以给节点绑定object:moving事件,实时调整关联线条的坐标:

this.canvas.on('object:moving', (opt) => {
  const node = opt.target;
  if (!(node as any).isStateNode) return;
  // 遍历所有线条,更新和当前节点关联的线条
  this.canvas.getObjects().forEach(obj => {
    if (obj.type === 'line') {
      const line = obj as any;
      if (line.fromNode === node) {
        line.set({ x1: node.left + node.width/2, y1: node.top + node.height/2 });
      } else if (line.toNode === node) {
        line.set({ x2: node.left + node.width/2, y2: node.top + node.height/2 });
      }
    }
  });
  this.canvas.renderAll();
});

内容的提问来源于stack exchange,提问作者Alfred Francis

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.26 10:37:17