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

如何在React Flow中实现类似draw.io的可移动可选择连线

在React Flow中实现可选中、可拖拽调整的自定义连线

需求:让React Flow里连接节点的路径线条具备可选中和拖拽调整路径的特性,达到类似draw.io流程图的效果——能拖动线条改变路径位置,且线条支持选中状态。

当前自定义连线实现代码:

import { getBezierPath, Position, Edge } from 'reactflow'

// 库未导出该类型,自行定义
interface GetBezierPathParams {
  sourceX: number
  sourceY: number
  sourcePosition?: Position
  targetX: number
  targetY: number
  targetPosition?: Position
  curvature?: number
}

export default function BendyEdge({
  id,
  sourceX,
  sourceY,
  targetX,
  targetY,
  sourcePosition,
  targetPosition,
  style,
}: Edge & GetBezierPathParams) {
   const midPoint = (targetY - sourceY) / 2 + sourceY
   // 示例中硬编码了偏移值,后续可动态计算
   const [edgePath] = getBezierPath({
   sourceX,
   sourceY,
   sourcePosition,
   targetX: targetX + 150,
   targetY: midPoint,
   targetPosition,
 })
 const [edgePath2] = getBezierPath({
   sourceX: targetX + 150,
   sourceY: midPoint,
   sourcePosition,
   targetX,
   targetY,
   targetPosition,
 })

 return (
   <>
     <path id={id} style={style} className="react-flow__edge-path" d={edgePath + edgePath2} />
   </>
 )
}

连线效果示意图:
连线效果示意图


实现方案

1. 启用连线选中与状态响应

React Flow默认支持连线选中,自定义组件可通过selected属性(从Edge props中获取)切换样式,比如选中时加粗、变色,明确反馈选中状态。

2. 添加可拖拽的路径控制点

要调整连线路径,需给贝塞尔曲线添加可拖拽的中间锚点。在连线中间位置添加圆形拖拽手柄,拖动时更新锚点坐标,进而重新计算路径。

3. 处理拖拽事件更新路径

用React状态保存中间锚点坐标,监听拖拽手柄的鼠标事件,在拖动过程中实时更新坐标,重新生成贝塞尔路径。

4. 完整修改后的代码

import { useState, useRef } from 'react';
import { getBezierPath, Position, Edge, useReactFlow } from 'reactflow';

// 库未导出该类型,自行定义
interface GetBezierPathParams {
  sourceX: number;
  sourceY: number;
  sourcePosition?: Position;
  targetX: number;
  targetY: number;
  targetPosition?: Position;
  curvature?: number;
}

export default function BendyEdge({
  id,
  sourceX,
  sourceY,
  targetX,
  targetY,
  sourcePosition,
  targetPosition,
  style,
  selected,
}: Edge & GetBezierPathParams) {
  // 保存中间锚点坐标,初始值基于节点位置计算
  const [midAnchor, setMidAnchor] = useState({
    x: targetX + 150,
    y: (targetY - sourceY) / 2 + sourceY,
  });
  const isDragging = useRef(false);
  const { unproject } = useReactFlow();

  // 计算两段贝塞尔路径
  const [edgePath1] = getBezierPath({
    sourceX,
    sourceY,
    sourcePosition,
    targetX: midAnchor.x,
    targetY: midAnchor.y,
    targetPosition: Position.Right,
  });
  const [edgePath2] = getBezierPath({
    sourceX: midAnchor.x,
    sourceY: midAnchor.y,
    sourcePosition: Position.Left,
    targetX,
    targetY,
    targetPosition,
  });

  // 拖拽事件处理
  const handleDragStart = (e: React.MouseEvent) => {
    e.stopPropagation();
    isDragging.current = true;
    document.addEventListener('mousemove', handleDrag);
    document.addEventListener('mouseup', handleDragEnd);
  };

  const handleDrag = (e: MouseEvent) => {
    if (!isDragging.current) return;
    const { x, y } = unproject({ x: e.clientX, y: e.clientY });
    setMidAnchor({ x, y });
  };

  const handleDragEnd = () => {
    isDragging.current = false;
    document.removeEventListener('mousemove', handleDrag);
    document.removeEventListener('mouseup', handleDragEnd);
  };

  // 选中状态样式
  const edgeStyle = {
    ...style,
    strokeWidth: selected ? 3 : 2,
    stroke: selected ? '#2563eb' : '#94a3b8',
    cursor: 'pointer',
  };

  return (
    <>
      <path
        id={id}
        style={edgeStyle}
        className="react-flow__edge-path"
        d={`${edgePath1} ${edgePath2}`}
      />
      {/* 仅在选中时显示拖拽手柄 */}
      {selected && (
        <circle
          cx={midAnchor.x}
          cy={midAnchor.y}
          r={6}
          fill="#2563eb"
          stroke="#ffffff"
          strokeWidth={2}
          style={{ cursor: 'move' }}
          onMouseDown={handleDragStart}
        />
      )}
    </>
  );
}

5. 额外优化建议

  • 可添加多个控制点,支持更复杂的路径调整;
  • 拖拽时增加防抖逻辑,避免频繁重绘;
  • 补充触摸事件监听,适配移动端操作;
  • 将中间锚点坐标存入Edge的data属性,实现路径状态的持久化(可随流程图导出/保存)。

内容的提问来源于stack exchange,提问作者Rizwan Saleem

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 21:55:10