如何在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
相关产品推荐
相关产品推荐

