跳到主要内容

流程图与节点编辑器

流程图的真相是 节点表 + 边表,几何是投影。<g> 上的 transform 是视图,不是数据库。连线存成截图像素等于没有文档。世界单位自定(常见 1 = 1 CSS 像素 at scale=1);相机是根 viewBox

1. 场景怎么切

文档:Node[] / Edge[](id、父级、端口、标签)
世界:node.x/y/w/h、edge 的 path 控制点
相机:viewBox = 视口在世界里的矩形
层:
g.edges 边,z 在节点下
g.nodes 节点外壳 + 端口
g.overlay 选中框、对齐线、正在拉的边(不进导出)
DOM:节点标题 IME、右键菜单
小地图:可以是第二相机的光栅

必须是节点:业务节点、端口、可点标签、可改的边。命中、框选、对齐、撤销都认这些 id。

可以光栅:小地图、网格点、超长画布上未进视口的页。正在拖的橡胶筋可以临时 canvas,pointerup 写回 path

export interface FlowNode {
id: string;
x: number;
y: number;
w: number;
h: number;
label: string;
}

export interface FlowEdge {
id: string;
from: { nodeId: string; port: string };
to: { nodeId: string; port: string };
}

export interface FlowDoc {
nodes: FlowNode[];
edges: FlowEdge[];
}

export function cameraToViewBox(
cam: { x: number; y: number; scale: number },
cssW: number,
cssH: number,
): string {
return `${cam.x} ${cam.y} ${cssW * cam.scale} ${cssH * cam.scale}`;
}

边的几何从端口世界坐标算贝塞尔,存在边对象里或每次从端口派生。派生更抗「挪节点忘了改线」。存副本就要在 SetNodePos 命令里同时更新边。

2. 交互

框选、多选、变换手柄走文档命中:先 viewToWorldgetScreenCTM 逆),再 AABB / 端口半径。不要对整棵树 elementFromPoint 然后还自己算一次偏移——两者会在 stroke 扩展、透明区域上打架。见 指针手柄

拖节点:pointerdown 记起点,move 用 coalesce 命令改 x/y,up 提交。撤销一步回到拖之前,见 撤销

端口几何从节点矩形派生,不要另存一份屏幕坐标。连线 d 在世界空间生成,线宽若要屏幕恒定,用 non-scaling-stroke 或 overlay。

3. 不适用

  • 节点内部是富文本页面:外壳 SVG,内容 foreignObject 或 HTML overlay。用户上传的 foreignObject 仍按 XSS 禁。
  • 自动布局一次烘成图:布局结果要写回 x/y,否则下次打开会跳。
  • 把边画在 canvas 上当唯一存储:缩放即糊,命中要对齐两套表面。

4. 失败形态

症状原因
缩放后线对不上端口边存在了屏幕坐标
导出没有连线线画在 overlay / canvas,没进文档
拖一下 undo 40 次没 coalesce
框选漏了细线命中只用 fill,忽略了 stroke 宽度
打印只有节点没有字字在 HTML overlay 且打印 CSS 藏了 DOM

权威资料

核对日期:2026-08-26