X6 MiniMap 插件实战指南:为图形画布添加可交互的小地图导航
【免费下载链接】X6🚀 JavaScript diagramming library that uses SVG and HTML for rendering.项目地址: https://gitcode.com/GitHub_Trending/x6/X6
MiniMap 是 X6 官方提供的一颗图编辑插件,它在主画布之外渲染一个缩略视图,让用户通过移动/缩放小地图视口即可快速定位与缩放主画布,非常适合大图、长流程编辑场景。阅读本文后,你将掌握 MiniMap 的接入方式、全部配置项含义,以及它在源码层的工作机制(内部 Graph 复用模型、视口比例换算、与 Scroller 的联动),并能直接复刻官方示例中的"简单视图/详细视图"切换能力。
MiniMap 是什么
MiniMap(小地图)插件为Graph实例提供一块独立的缩略图区域:它把当前画布中的所有节点、边渲染到一个更小的内部 Graph 中,并在其上叠加一个高亮"视口"(viewport)矩形,用于表示主画布当前可见区域。通过小地图,用户可以:
- 拖动小地图视口来平移主画布;
- 拖动视口右下角的缩放圆柄来缩放主画布(在
scalable开启时)。
该功能在 X6 中由 src/plugin/minimap/index.ts 中的MiniMap类实现,类名name = 'minimap',实现了GraphPlugin接口,因此可以通过graph.use(...)安装,并由 src/plugin/index.ts 作为MiniMap统一导出。
基本使用
启用小地图非常简单:从@antv/x6导入MiniMap,实例化后通过graph.use()挂载到图上。官方教程给出的最小示例:
import { Graph, MiniMap } from '@antv/x6' const graph = new Graph({ background: { color: '#F2F7FA', }, }) graph.use( new MiniMap({ container: document.getElementById('minimap'), }), )其中container是唯一的必选参数,用于指定小地图挂载的 DOM 容器。MiniMap 会在该容器内创建自己的 DOM 结构(容器根节点x6-widget-minimap、内部 Graph 区域与视口层),无需额外编写样式。
交互行为
官方演示(演示源码)展示了小地图的两类核心交互:
- 移动小地图视口来移动画布:鼠标按下视口区域并拖拽时,主画布会同步平移(若安装了 Scroller 插件,则移动的是滚动容器的滚动位置)。
- 缩放小地图视口来缩放画布:视口右下角有一个圆形缩放手柄,拖动它即可连续缩放主画布,缩放范围由
minScale与maxScale约束。
在源码中,这两类交互由startAction→doAction→stopAction这套事件流完成(见 src/plugin/minimap/index.ts):
mousedown/touchstart触发startAction,根据事件目标是视口还是缩放手柄,将action标记为panning或zooming,并记录初始坐标、滚动位置、缩放值与视口几何信息;- 之后的
mousemove/touchmove进入doAction:panning分支根据鼠标位移换算成主画布的平移量(或滚动容器的scrollLeft/scrollTop),zooming分支通过requestAnimationFrame按位移增量调用sourceGraph.zoom(delta, { absolute: true, minScale, maxScale }); mouseup/touchend触发stopAction,解绑文档级事件并结束本次操作。
另外,在小地图空白区域(内部 Graph 区域)直接点击,会触发scrollTo,将主画布中心移动到对应位置(src/plugin/minimap/index.ts)。
配置选项
MiniMap 支持以下配置项,下表完整来自官方文档,并已对照 src/plugin/minimap/type.ts 与 src/plugin/minimap/index.ts 中的DefaultOptions进行核实:
| 属性名 | 类型 | 默认值 | 必选 | 描述 |
|---|---|---|---|---|
| container | HTMLElement | - | ✓ | 挂载小地图的容器 |
| width | number | 300 | 小地图的宽度 | |
| height | number | 200 | 小地图的高度 | |
| padding | number | 10 | 小地图容器的 padding 边距 | |
| scalable | boolean | true | 是否启用缩放(false 时隐藏缩放手柄) | |
| minScale | number | 0.01 | 最小缩放比例 | |
| maxScale | number | 16 | 最大缩放比例 | |
| graphOptions | Graph.Options | {} | 小地图内部 Graph 的选项 | |
| createGraph | (options: Graph.Options) => Graph | options => new Graph(options) | 自定义创建小地图内部 Graph 的方法 |
各选项的实际作用:
container:必填,小地图挂载的容器,源码中通过this.options.container.appendChild(this.container)插入;width/height/padding:决定小地图根容器的尺寸与内边距,同时参与视口比例计算(maxWidth = width - 2 * padding,maxHeight = height - 2 * padding,见updatePaper,src/plugin/minimap/index.ts);scalable:为true时才会创建视口右下角的缩放圆柄(zoomHandle,src/plugin/minimap/index.ts);minScale/maxScale:在doAction的zooming分支中作为graph.zoom(..., { minScale, maxScale })的参数传入,用于限制缩放范围;graphOptions:透传给内部小地图 Graph 的选项(见下文"内部实现");createGraph:默认是options => new Graph(options),可替换为自定义 Graph 工厂方法(如接入自定义子类)。
内部实现:小地图如何工作
了解实现细节有助于在复杂场景下正确使用 MiniMap。以下是 src/plugin/minimap/index.ts 的核心机制:
内部 Graph 与模型共享
MiniMap在初始化时会创建一个内部目标 Graph(targetGraph),其关键点是与源 Graph 共享同一个 model(model: this.sourceGraph.model),因此主画布上的任何增删改都会自动反映在小地图中。同时,内部 Graph 会强制关闭大部分交互与辅助能力(见 src/plugin/minimap/index.ts):
const targetGraphOptions: Options = { ...this.options.graphOptions, container: graphContainer, model: this.sourceGraph.model, interacting: false, grid: false, background: false, embedding: false, panning: false, }视口比例换算
updatePaper会根据源画布与容器的尺寸计算缩放比例:
ratio = Math.min(maxWidth / width, maxHeight / height)即在小地图可用区域内等比放下整张画布,随后对内部 Graph 执行resize、translate与scale(有 Scroller 时直接scale(ratio, ratio),否则zoomToFit()),并在源画布resize时同步更新。这也解释了为什么小地图始终能看到整张图的缩略全貌。
事件联动
startListening(src/plugin/minimap/index.ts)按是否安装 Scroller 插件区分了监听策略:
- 安装了 Scroller:直接监听滚动容器的
scroll事件来更新视口位置; - 未安装 Scroller:监听源 Graph 的
translate、scale事件,以及model:updated事件(模型更新时对内部 Graph 执行zoomToFit)。
视口位置的更新(updateViewport)使用了FunctionExt.debounce(..., 0)防抖,避免高频事件导致布局抖动,并通过计算源画布可见区域在小地图坐标系中的几何信息(top/left/width/height)来设置视口 div 的 CSS。
视觉样式
小地图的外观由 src/plugin/minimap/index.less(运行时通过CssLoader.ensure('minimap', content)注入)控制,关键样式包括:
- 根容器
x6-widget-minimap:overflow: hidden、居中显示、白色背景; - 视口
x6-widget-minimap-viewport:2px solid #31d0c6青绿色边框、cursor: move,并通过margin: -2px 0 0 -2px抵消边框对外部布局的影响; - 缩放手柄
x6-widget-minimap-viewport-zoom:12×12 的白色圆形圆柄,位于视口右下角,cursor: nwse-resize。
进阶:自定义小地图视图(简单视图)
官方演示还展示了小地图的高级用法:通过graphOptions.createCellView自定义内部 Graph 对单元格的渲染方式,实现"简单视图 / 详细视图"切换(site/src/tutorial/plugins/minimap/index.tsx)。
this.graph.disposePlugins('minimap') // 先销毁已有小地图 this.graph.use( new MiniMap({ container: this.minimapContainer, width: 200, height: 160, padding: 10, graphOptions: { createCellView(cell) { // 返回三种类型数据: // 1. null: 不渲染该单元格 // 2. undefined: 使用 X6 默认渲染方式 // 3. CellView: 自定义渲染 if (cell.isEdge()) { return null // 不渲染边,小地图更干净 } if (cell.isNode()) { return SimpleNodeView // 自定义节点视图 } }, }, }), )配套的SimpleNodeView(site/src/tutorial/plugins/minimap/simple-view.tsx)继承自NodeView,只渲染一个<rect>并以灰色填充,从而获得更"扁平"的缩略效果:
export class SimpleNodeView extends NodeView { protected renderMarkup() { return this.renderJSONMarkup({ tagName: 'rect', selector: 'body', }) } update() { super.update({ body: { refWidth: '100%', refHeight: '100%', fill: '#8f8f8f' }, }) } }这种"简单视图"在节点数量极大的场景下能显著提升小地图的渲染与交互性能。
插件生命周期与 API
MiniMap 作为标准 Graph 插件,支持完整的生命周期管理(实现在 src/graph/graph.ts):
graph.use(new MiniMap({ ... })):安装插件,等价于调用plugin.init(graph, ...options);graph.getPlugin('minimap'):获取 MiniMap 实例(注意插件名固定为'minimap');graph.disposePlugins('minimap'):销毁插件,会调用MiniMap.dispose(),依次执行remove()(内部会stopListening()并targetGraph.dispose(false))与CssLoader.clean('minimap')(src/plugin/minimap/index.ts)。
在官方演示中,"简单视图/详细视图"切换正是通过disposePlugins('minimap')后重新use(new MiniMap(...))实现的,这是动态调整小地图渲染策略的推荐做法。
测试与验证
仓库内置了 MiniMap 的完整测试(tests/plugin/minimap.spec.ts),覆盖三类典型场景:
- 基础场景:安装 MiniMap 后添加带端口的节点,验证实例类型与 DOM 快照(
minimap-1.html/minimap-2.html),并验证addClass/removeClass方法; - 配合 Scroller:同时安装
Transform与Scroller,设置滚动位置、平移与缩放(graph.scale(0.1, 0.2))后校验快照(minimap-scroller.html); - 仅配合 Transform:不安装 Scroller,仅做
translate与scale,校验快照(minimap-transform.html)。
这些测试印证了 MiniMap 在"有 Scroller / 无 Scroller"两种布局模式下都能正确同步画布状态,可以作为你集成小地图时的行为基准。
完整可运行示例
结合官方演示(site/src/tutorial/plugins/minimap/index.tsx),一个同时使用 Scroller 与 MiniMap 的完整示例:
import { Graph, MiniMap, Scroller } from '@antv/x6' const graph = new Graph({ container: document.getElementById('container'), width: 600, height: 320, panning: false, background: { color: '#F2F7FA' }, }) // 先启用滚动容器,让主画布可平移、可滚动 graph.use( new Scroller({ enabled: true, pageVisible: true, pageBreak: false, pannable: true, }), ) // 再挂载小地图 graph.use( new MiniMap({ container: document.getElementById('minimap'), width: 200, height: 160, padding: 10, }), ) // 添加节点与边 graph.addNode({ x: 200, y: 100, width: 100, height: 40, label: 'Rect', attrs: { body: { stroke: '#8f8f8f', strokeWidth: 1, fill: '#fff', rx: 6, ry: 6 } }, }) const source = graph.addNode({ x: 32, y: 32, width: 100, height: 40, label: 'Hello', attrs: { body: { stroke: '#8f8f8f', strokeWidth: 1, fill: '#fff', rx: 6, ry: 6 } }, }) const target = graph.addNode({ shape: 'circle', x: 160, y: 180, width: 60, height: 60, label: 'World', attrs: { body: { stroke: '#8f8f8f', strokeWidth: 1, fill: '#fff' } }, }) graph.addEdge({ source, target, attrs: { line: { stroke: '#8f8f8f', strokeWidth: 1 } }, })运行后即可体验:拖动小地图视口平移画布,拖动视口右下角圆柄缩放画布,点击小地图任意位置将主画布中心定位到该处。建议在此基础上按需组合width/height/padding、minScale/maxScale与graphOptions.createCellView,为大图编辑场景打造流畅的导航体验。
【免费下载链接】X6🚀 JavaScript diagramming library that uses SVG and HTML for rendering.项目地址: https://gitcode.com/GitHub_Trending/x6/X6
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考