- 前端
- UI组件
【免费下载链接】joint
A proven SVG-based JavaScript diagramming library powering exceptional UIs
导读
本文围绕 JointJS 官方示例 hover-element-connect-tool-js 展开,讲解如何在元素上以"拖拽即连线"的方式创建链接,且连接工具仅在用户悬停到元素路径时出现、并跟随鼠标移动。读者读完本文后,将掌握elementTools.HoverConnect工具的配置方法、Paper的连接策略与校验配置,以及该工具在@joint/core源码中的底层实现原理,可直接复制到自己的 JointJS 项目中实现同类交互。
功能概览:什么是 HoverConnect
在常规 JointJS 应用中,连接工具通常常驻于元素上,随时可以拖拽出连线。而本示例解决的是一个更精细的交互需求:
需要以拖拽(drag & drop)方式从元素创建链接,但希望连接工具只在用户悬停到元素路径时才出现,并且工具按钮会出现在鼠标当前所处的位置。
其核心机制是:在每个元素上铺设一条"隐形轨道路径"(invisible track path),当鼠标进入该路径区域时,连接工具(一个圆形按钮)才显示出来;鼠标在轨道上移动时,按钮沿轨道跟随移动;点击按钮并拖拽即可拉出一条新的连线。全部行为由@joint/core提供的elementTools.HoverConnect工具实现,无需任何额外插件。
快速开始:安装、开发、构建与预览
示例位于 monorepo 的examples/hover-element-connect-tool-js目录,依赖@joint/core(workspace 引用)与 Vite。从仓库根目录安装并构建全部依赖:
yarn install yarn run build进入示例目录启动开发服务器:
cd examples/hover-element-connect-tool-js yarn dev终端会打印出本地地址(通常为http://localhost:5173),用浏览器打开即可交互体验。生成生产构建与本地预览:
yarn build # 产物输出到 dist/ 目录 yarn preview # 本地预览生产构建对应的脚本定义在 package.json 中:dev为vite,build为tsc && vite build,preview为vite preview。
示例结构
examples/hover-element-connect-tool-js/ ├── assets/jointjs-logo-black.svg # 页面角落的 JointJS Logo ├── src/ │ ├── main.js # 全部示例逻辑 │ └── styles.css # 画布全屏布局样式 ├── index.html # 页面入口,挂载 #paper-container ├── README.md └── package.jsonindex.html 中只包含一个全屏的#paper-container容器与页面 Logo,示例逻辑全部集中在 src/main.js。
核心实现剖析:从 Paper 配置到工具挂载
1. 创建 Graph 与 Paper
const graph = new dia.Graph({}, { cellNamespace: shapes }); const paper = new dia.Paper({ model: graph, cellViewNamespace: shapes, width: '100%', height: '100%', gridSize: 20, async: true, sorting: dia.Paper.sorting.APPROX, background: { color: '#F3F7F6' }, defaultLink: () => new shapes.standard.Link(), ... });画布开启了异步渲染(async: true)、近似排序(sorting: APPROX)、20px 网格,并使用defaultLink指定拖拽连线时默认创建的链接类型为shapes.standard.Link。
2. 连接校验与连接策略
validateConnection: (srcView, _, tgtView) => { const src = srcView.model; const tgt = tgtView.model; if (src.isLink() || tgt.isLink()) return false; if (src === tgt) return false; return true; }, defaultConnectionPoint: { name: 'anchor' }, connectionStrategy: (end, view, magnet, coords) => { const bbox = view.getNodeUnrotatedBBox(magnet); const p = bbox.pointNearestToPoint(coords); return connectionStrategies.pinRelative(end, view, magnet, p); }, snapLinks: { radius: 10 }, linkPinning: false这几项配置共同决定了拖拽连线的体验:
validateConnection:禁止从链接连到链接、也禁止元素自连,保证连线只发生在两个不同元素之间;defaultConnectionPoint:默认连接点使用anchor,让连线端点锚定在元素几何上;connectionStrategy:拖拽过程中,端点位置取鼠标坐标在磁铁(magnet)包围盒上的最近点,再通过connectionStrategies.pinRelative以相对比例固定(pin)到目标元素上;snapLinks: { radius: 10 }:当鼠标靠近可连接目标 10px 范围内时自动吸附;linkPinning: false:链接两端必须连接到元素,不能悬空。
此外paper.setGrid('mesh')将网格模式切换为 mesh,使拖拽出的连线端点随网格对齐。
3. 为元素挂载 HoverConnect 工具
const tools = new dia.ToolsView({ tools: [new elementTools.HoverConnect()] }); rectangle.findView(paper).addTools(tools);对每个矩形元素(示例中有两个shapes.standard.Rectangle),创建一个dia.ToolsView,放入一个elementTools.HoverConnect实例,再通过findView(paper).addTools(tools)挂到对应元素的视图上。这就是"悬停才显示连接按钮"的全部挂载逻辑——工具本身自带了轨道路径与鼠标跟随行为。
源码级原理:HoverConnect 是如何"悬停跟随"的
1. 两层结构:透明轨道 + 隐藏按钮
HoverConnect定义在 packages/joint-core/src/cellTools/HoverConnect.mjs(基类,适用于元素与链接),其children()渲染出两层结构:
track:一条stroke: transparent、stroke-width默认 15(由options.trackWidth控制)的隐形路径,cursor: pointer,负责接收鼠标事件;button:一个display: none的组,内含默认的圆形按钮与连线图标 markup(深色圆 + 白色十字箭头),初始隐藏。
也就是说,"只有悬停才显示工具"是通过button的显隐切换实现的,轨道本身始终存在以捕获悬停。
2. 事件流:进入、移动、离开
events: Object.assign({ mousemove: 'onMousemove', mouseenter: 'onMouseenter', mouseleave: 'onMouseleave' }, Connect.prototype.events)onMouseenter:若canShowButton()(Paper 事件仍处于委托状态)为真,则showButton()显示按钮;onMousemove:调用getTrackRatioFromEvent(evt)把鼠标坐标投影到轨道路径上得到长度比例,存入options.distance,再调用position()让按钮沿路径移动;onMouseleave:hideButton()隐藏按钮。
getTrackRatioFromEvent的实现要点:先把clientX/clientY通过paper.clientToLocalPoint转为本地坐标,再经轨道矩阵逆变换,最后用trackPath.closestPointLength(trackPoint)得到鼠标在路径上的最近投影长度——这正是"按钮跟随鼠标"的数学基础。
3. 按钮定位:沿路径切线的矩阵计算
position()通过getButtonMatrix()计算按钮变换矩阵:在轨道路径上取options.distance(默认 0,支持百分比,如'50%')处对应的切线,以切线起点为位置、切线方向为旋转角,再叠加offset平移与可选scale缩放;当rotate为 false 时取消路径方向的旋转。轨道自身的矩阵由getTrackMatrix()提供。绘制时机在update()中:getTrackPath()得到路径数据后,通过childNodes.track.setAttribute('d', ...)写入轨道。
4. 元素场景的轨道路径:calc 表达式
元素版的elementTools.HoverConnect(packages/joint-core/src/elementTools/HoverConnect.mjs)覆写了getTrackPath():默认轨道路径为
M 0 0 H calc(w) V calc(h) H 0 Z即绕元素包围盒一周的矩形轮廓。calc(w)、calc(h)会被代入元素的实际宽高求值;trackPath也支持传入函数(接收元素视图,返回任意 SVG path 数据),从而可把轨道定制为任意形状——例如只沿元素某条边或某个子路径悬停。
元素版还新增了若干选项:
useModelGeometry:决定使用模型几何还是视图几何计算包围盒;relative:calc 表达式中宽高是否相对于视图包围盒;rotate:轨道矩阵是否随元素旋转角旋转(getTrackMatrixAbsolute()会以包围盒中心为原点构造矩阵)。
测试代码 packages/joint-core/test/ts/toolsView.test.ts#L60-L64 给出了这些选项的合法用法:
new elementTools.HoverConnect({ useModelGeometry: true, trackWidth: 10, trackPath: (view) => view.model.attr(['body', 'd']), });其中trackPath返回元素body子元素的d属性,即"沿着元素实际形状的轮廓悬停",是定制化路径的一个典型范式。
5. 拖拽连线:继承自 Connect 的文档级事件
HoverConnect继承自Connect(packages/joint-core/src/cellTools/Connect.mjs),拖拽行为全部复用自后者:
documentEvents: { mousemove: 'drag', touchmove: 'drag', mouseup: 'dragend', touchend: 'dragend', touchcancel: 'dragend' }按下按钮触发dragstart(relatedView.dragLinkStart(...)),随后在 document 级监听mousemove/mouseup完成dragLink与dragLinkEnd,支持鼠标与触摸两种输入。options.magnet可指定拖拽连线的磁铁节点(默认取元素视图根节点,也支持选择器字符串或 SVGElement),options.action可自定义按下时的行为。得益于validateConnection、snapLinks与connectionStrategy的组合,拖拽出的链接会自动吸附并固定到目标元素上。
常用配置选项速查
| 选项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
trackWidth | number | 15 | 隐形轨道的描边宽度,即悬停热区宽度 |
distance | number | string | 0 | 按钮在轨道上的初始位置,支持'50%'等百分比 |
offset | number | 0 | 按钮相对路径切线的偏移量 |
rotate | boolean | — | 按钮是否随路径切线方向旋转 |
scale | number | — | 按钮缩放系数 |
markup | array | 默认圆形+箭头 | 自定义按钮的 SVG markup |
magnet | function | string | SVGElement | 元素视图根节点 | 拖拽连线的磁铁节点 |
action | function | 触发dragstart | 按下按钮时的动作 |
trackPath | string | function | 'M 0 0 H calc(w) V calc(h) H 0 Z' | 元素版:轨道路径数据或返回路径的函数 |
useModelGeometry | boolean | — | 元素版:使用模型几何计算包围盒 |
relative | boolean | — | 元素版:calc 表达式是否相对视图包围盒 |
样式说明
src/styles.css 将#paper-container设为绝对定位并撑满四边(overflow: scroll),使画布铺满整个视口;页面角落的 Logo 添加了白底、边框与投影的浮层样式。示例本身不依赖任何外部 UI 库。
小结
通过本示例,可以总结出在 JointJS 中实现"悬停式连接工具"的完整套路:
- 用
dia.ToolsView+elementTools.HoverConnect为元素挂载工具; - 通过
trackPath(calc 表达式或函数)定制悬停轨道形状,用trackWidth控制热区宽度; - 配合 Paper 的
validateConnection、connectionStrategy、snapLinks、linkPinning等配置,获得完整、可校验的拖拽连线体验; - 拖拽交互复用
Connect的文档级事件,天然支持触摸输入。
需要更深入的实现细节,可继续阅读 cellTools/HoverConnect.mjs、elementTools/HoverConnect.mjs 与 Connect.mjs 三份源码,它们共同构成了 HoverConnect 的完整行为闭环。
- 前端
- UI组件
【免费下载链接】joint
A proven SVG-based JavaScript diagramming library powering exceptional UIs
相关推荐
JointJS 悬停链路连接工具(Hover Connect Tool)实战指南:从链路拖拽创建新连接
JointJS 悬停链路连接工具(Hover Connect Tool)实战指南:从链路拖拽创建新连接 本篇技术指南以 JointJS 官方示例 hover l
前端UI组件JointJS 曲线连接器(Curve Connector)实战:用动态曲线连接元素
JointJS 曲线连接器(Curve Connector)实战:用动态曲线连接元素 JointJS 的 curve 连接器(connector)能够生成一条随
前端UI组件NonSteamLaunchers SD卡支持完全指南:释放你的Steam Deck存储空间
NonSteamLaunchers SD卡支持完全指南:释放你的Steam Deck存储空间 NonSteamLaunchers是一款专为Steam Deck设
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考