JavaScript is required

沿线条运动的 HTML DIV 覆盖层

这个示例展示如何把选中的 relation-graph 连线转换为 CSS motion-path 数据,并在画布上方挂载真实 HTML 覆盖层。它会高亮所选连线,在点击和拖拽节点后刷新路径,并允许用户在线路上切换“沿线运动”与“固定位置”两种表现。

图边上的沿线移动 HTML 覆盖层

这个示例实现了什么

这个示例在深色、霓虹风格的画布上渲染了一个紧凑的居中布局图,并默认保持一条边处于选中状态。被选中的边会得到更明亮的动画高亮、两端的自定义箭头标记,以及两个渲染在画布上方的 HTML 装饰元素:一个可以沿着边移动或停在固定百分比位置的主 DIV,以及一个持续沿同一路径运动的较小粒子。

用户可以点击另一条线来重新指定覆盖层目标,拖动节点并观察覆盖层如何与新的几何形状重新对齐,切换所有线所使用的形状,并打开共享设置面板来调整滚轮或拖拽行为以及导出图像。这个示例的核心并不是自定义 SVG 边渲染,而是如何把 relation-graph 内置的线条几何信息复用为普通 DOM 内容可用的 CSS motion-path 数据。

数据是如何组织的

图数据在 initializeGraph() 中以内联方式组装为一个静态 RGJsonData 对象,其中包含 rootIdnodes 数组和 lines 数组。每条线都有稳定的 idfromto 和 label,并且在调用 setJsonData(...) 加载图之前,会经过一段简短的预处理,为其补充 startMarkerIdendMarkerIdshowStartArrow

这里没有远程获取数据,也没有在布局前进行复杂转换。在生产应用中,同样的数据形态可以表示流程流转、设备连接、路由区段、依赖边或审批迁移,此时 line id 就成为把 DOM 覆盖层或状态徽标附着到某条被选中关系上的句柄。

relation-graph 的使用方式

index.tsx 使用 RGProvider 包裹整个示例,这让 RGHooks.useGraphInstance 和共享的 CanvasSettingsPanel 可以解析当前激活的图上下文。图配置使用了 layoutName: 'center'disableAsForceLayout: truedefaultJunctionPoint: RGJunctionPoint.lr,以及一个由状态驱动的 defaultLineShape,因此示例会从一个稳定的居中查看器开始,并且可以在运行时重新设置当前线条的样式。

图实例 API 驱动了主要行为。setJsonData(...)moveToCenter()zoomToFit() 用于初始化视图;getLineById(...) 选择 line-1 作为初始被跟踪的边;getLines()updateLine(...) 负责管理被选中线条的高亮和形状切换;generateLineConfig(...)generateLinePath(...) 则把当前线条转换为 CSS path(...) 字符串。这个路径随后被传入 RGSlotOnCanvasAbove 内部的一个包装器,因此内置的 relation-graph 边渲染器会继续保持可见,而 HTML 覆盖层则在其上方移动。

其余的定制都在本地完成。MySvgDefs 注入了胶状滤镜和自定义 marker 定义,my-relation-graph.scss 覆盖了节点和线条样式,并将两个覆盖层都绑定到 offset-path,而共享的 DraggableWindowCanvasSettingsPanel 工具则提供了浮动控制窗口、滚轮和拖拽模式切换,以及图像导出流程。这个示例仍然偏向查看器场景而不是编辑器场景:用户改变的是几何和呈现,而不是图结构本身。

关键交互

  • 点击一条线会把它设为被跟踪路径,只高亮这一条线,并重新生成覆盖层所使用的 CSS 路径字符串。
  • 拖动节点会触发 onNodeDragEnd,重新计算被跟踪路径,使覆盖层继续附着在更新后的几何路径上。
  • 线条形状选择器会把当前所有边切换为直线、曲线、折线和贝塞尔样式之一,然后刷新被跟踪路径。
  • 位置选择器会把主 DIV 在持续动画与固定停靠点之间切换,固定停靠点可以是选中路径上的 10%、25%、50%、75% 或 90%。
  • 浮动窗口可以拖动,其中的设置覆盖层可以改变滚轮和画布拖拽行为,并导出当前图像。

关键代码片段

这段初始化代码展示了内联数据集、对每条线的 marker 分配,以及默认选中 line-1 的过程。

myJsonData.lines.forEach(line => {
  line.endMarkerId = 'my-arrow-001';
  line.startMarkerId = 'my-arrow-001-start';
  line.showStartArrow = true;
});
await graphInstance.setJsonData(myJsonData);
graphInstance.moveToCenter();
graphInstance.zoomToFit();
const line = graphInstance.getLineById('line-1');
if (line) {
  handleLineSelection(line);
}

这段代码展示了核心的可复用技巧:它会高亮一条内置线条,并把该线条转换为供 DOM 覆盖层使用的 CSS path(...) 字符串。

currentLineObject.current = line;
for (const l of graphInstance.getLines()) {
  graphInstance.updateLine(l, {
    className: l.id === line.id ? 'my-moving-line' : ''
  });
}
const lineConfig = graphInstance.generateLineConfig(line);
if (lineConfig) {
  const pathInfo = graphInstance.generateLinePath(lineConfig);
  setDivOffsetPath(`path('${pathInfo.pathData}')`);
}

这段更新路径说明,线条形状变化是在当前图上运行时完成的,随后会为当前选中的覆盖层目标重新生成路径。

const setNewLineShape = async (newShape: number) => {
  setLineShape(newShape);
  const lines = graphInstance.getLines();
  lines.forEach(line => {
    graphInstance.updateLine(line, {
      lineShape: newShape
    });
  });
  if (currentLineObject.current) {
    handleLineSelection(currentLineObject.current!);
  }
};

这段 CSS 片段展示了两个 DOM 元素如何绑定到生成出来的路径上,而不是绑定到固定的屏幕坐标上。

.div-on-line {
  animation: my-line-move 3s linear infinite;
  position: absolute;
  z-index: 999;
  offset-path: var(--checked-line-path);
  pointer-events: none;
}

.effect-particle {
  offset-path: var(--checked-line-path);
  offset-rotate: auto;
  animation: fly-along-path 3s cubic-bezier(0.4, 0.0, 0.2, 1) infinite;
}

这个示例的独特之处

准备好的对比数据把这个示例放在 custom-line-animationcustom-line-stylecustomer-line1diy-line-arrowadv-line-slot 附近,但它强调的是另一层定制能力。与 adv-line-slotcustomer-line1 相比,这个示例保留了 relation-graph 原生可见的线条渲染器,并在画布上方附加 DOM 装饰,而不是用自定义 line slot 完全替换边渲染。

它更有辨识度的组合在于这种感知几何变化的附着流程。被选中的线条会通过 generateLineConfig(...)generateLinePath(...) 转换成 CSS motion-path 数据,并且这个路径会在线条点击、节点拖拽结束和线条形状变化之后重新生成。对比记录将这种“选中线条跟踪 + 路径刷新 + 可动画或固定位置的 HTML 覆盖层”的组合标记为少见,尤其是当它还配合了自定义起止 marker、胶状高亮边,以及可拖拽的工具窗口时。

custom-line-animationcustom-line-style 相比,它的运行时行为更收敛,但对几何信息的感知更强:这个示例的重点并不是整个图的样式展示。与 diy-line-arrow 相比,自定义 marker 只是辅助细节,而不是主要教学点。因此,当需求是“把一个真实 DOM 元素附着到某一条动态 relation-graph 边上,并在图发生变化时持续保持对齐”时,div-on-line 是一个更强的起点。

这种模式还适用于哪些场景

这种模式很适合监控和工作流界面,在这些界面中,某一条关系往往需要比标准 SVG 更丰富的 DOM 呈现。服务地图可以在被选中的依赖边上放置实时状态芯片,物流视图可以把车辆徽标停靠在固定百分比位置,或让它沿当前激活的路径区段移动,而制造流程图则可以让告警标签始终附着在当前正在检查的连接上。

它同样适用于引导式说明层。工作流或审批图可以把 DOM tooltip、徽标或操作控件放在当前聚焦的迁移上,而知识图谱或拓扑视图则可以在不替换每一条边的内置线条渲染器的前提下,让一个标记沿着被选中的关系运动。