- 数据可视化
- 前端
- 图表库
【免费下载链接】G6
♾ A Graph Visualization Framework in JavaScript.
G6(@antv/g6)的事件系统是在底层图形渲染引擎 G 为主线,结合仓库源码(事件常量定义、Graph 运行时实现、BehaviorController 事件转发、插件与测试用例),系统讲解 G6 事件分类、监听/解绑/手动触发的完整 API,以及事件对象的结构与底层分发原理,帮助你掌握在 G6 中实现节点点击、画布拖拽、生命周期钩子等交互逻辑的完整方案。
Overview:G6 事件系统概览
G6 的事件系统并非从零实现,而是基于 G(AntV 图形渲染引擎)的事件系统封装而成,在其之上扩展了更全面的事件类型和更方便的事件绑定/解绑方法。从源码结构看,这一封装主要体现在两个层面:
- 事件常量枚举:G6 将所有事件名以枚举形式集中定义在 packages/g6/src/constants/events/index.ts 中,并通过 packages/g6/src/exports.ts 对外导出
CanvasEvent、ComboEvent、CommonEvent、ContainerEvent、EdgeEvent、GraphEvent、HistoryEvent、NodeEvent等枚举,开发者可以从@antv/g6直接引入使用。 - 统一的监听 API:
Graph实例继承自EventEmitter(见 packages/g6/src/runtime/graph.ts),on/once/off/emit四个方法在 Graph 上对外暴露,实际委托给@antv/event-emitter完成底层的事件注册与派发。
事件分类:Graph、Canvas 与 Element
G6 的事件类型主要分为以下三大类:
- Graph 事件(图实例事件):与整个图实例生命周期相关的事件,例如渲染完成事件、更新事件等;
- Canvas 事件(画布事件):与画布相关的事件,例如画布点击、画布拖拽等;
- Element 事件(元素事件):发生在元素对象上的事件,例如节点拖拽、边点击等。
下面分别展开说明。
Graph 事件(GraphEvent)
Graph 事件与图实例的整个生命周期绑定,例如beforecanvasinit(画布初始化前)、beforerender/afterrender(渲染开始前/完成后)、beforelayout/afterlayout(布局前后)、beforedestroy/afterdestroy(销毁前后)等。完整的 Graph 事件清单可参见 packages/site/docs/api/event.en.md 中的 Graph 生命周期事件(GraphEvent)章节。
以监听图渲染完成事件为例:
import { Graph, GraphEvent } from '@antv/g6'; const graph = new Graph({ // 图配置 }); graph.on(GraphEvent.AFTER_RENDER, () => { // 渲染完成后的回调 console.log('graph rendered'); });为什么监听 AFTER_RENDER 很重要?从 packages/g6/src/runtime/graph.ts 的render()实现可以看到,render是异步方法(内部会执行数据更新、元素绘制、布局),官方注释明确指出:"render 为异步方法,如果需要在 render 后执行一些操作,可以使用await graph.render()或者监听 GraphEvent.AFTER_RENDER 事件"。源码在渲染流程开头emit(this, new GraphLifeCycleEvent(GraphEvent.BEFORE_RENDER))(第 1198 行),在所有绘制、布局、自适应完成后emit(this, new GraphLifeCycleEvent(GraphEvent.AFTER_RENDER))(第 1213 行)。因此监听AFTER_RENDER是拿到"图已就绪"信号的最可靠方式。
Canvas 事件(CanvasEvent)
Canvas 事件与画布本身相关,例如canvas:click(点击画布空白区域)、canvas:drag(拖拽画布)、canvas:wheel(滚轮缩放)等。完整清单参见 packages/site/docs/api/event.en.md 的 Canvas 事件(CanvasEvent)章节。
监听画布点击事件:
import { Graph, CanvasEvent } from '@antv/g6'; const graph = new Graph({ // 图配置 }); graph.on(CanvasEvent.CLICK, (event) => { // 点击画布空白区域 console.log('canvas clicked'); });Element 事件(NodeEvent / EdgeEvent / ComboEvent)
Element 事件主要指在元素对象上触发的事件,例如节点的拖拽事件、边的点击事件。元素分为三类:节点(node)、边(edge)与 Combo(combo),对应的事件枚举为:
NodeEvent(节点事件)EdgeEvent(边事件)ComboEvent(Combo 事件)
与 Canvas 事件的监听方式一致,例如监听节点的拖拽事件、边的点击事件:
import { Graph, NodeEvent, EdgeEvent, ComboEvent } from '@antv/g6'; const graph = new Graph({ // 图配置 }); graph.on(NodeEvent.DRAG, (event) => { // 节点拖拽中 }); graph.on(EdgeEvent.CLICK, (event) => { // 边被点击 }); graph.on(ComboEvent.CLICK, (event) => { // Combo 被点击 });事件命名规范与常量枚举
G6 的事件名遵循[object]:[event]的命名格式,例如:
node:click—— 节点点击事件edge:mouseenter—— 鼠标移入边事件canvas:drag—— 画布拖拽事件
G6 为每一类事件提供了完整的常量枚举(定义于 packages/g6/src/constants/events/ 目录下的node.ts、edge.ts、combo.ts、canvas.ts、graph.ts、common.ts、container.ts、history.ts、animation.ts),官方 API 文档强烈推荐使用这些常量而非直接书写字符串事件名,其优势在于:
- 类型安全:避免事件名字符串拼写错误;
- 智能提示:在 IDE 中可获得自动补全与代码提示。
节点事件(NodeEvent)
源码见 packages/g6/src/constants/events/node.ts:
| 常量名 | 事件名 | 说明 |
|---|---|---|
| CLICK | node:click | 点击节点时触发 |
| DBLCLICK | node:dblclick | 双击节点时触发 |
| POINTER_OVER | node:pointerover | 指针移入节点时触发 |
| POINTER_LEAVE | node:pointerleave | 指针移出节点时触发 |
| POINTER_ENTER | node:pointerenter | 指针移入节点或其子元素时触发(不冒泡) |
| POINTER_MOVE | node:pointermove | 指针在节点上移动时触发 |
| POINTER_OUT | node:pointerout | 指针移出节点时触发 |
| POINTER_DOWN | node:pointerdown | 指针在节点上按下时触发 |
| POINTER_UP | node:pointerup | 指针在节点上抬起时触发 |
| CONTEXT_MENU | node:contextmenu | 在节点上打开右键菜单时触发 |
| DRAG_START | node:dragstart | 开始拖拽节点时触发 |
| DRAG | node:drag | 拖拽节点过程中触发 |
| DRAG_END | node:dragend | 拖拽节点结束时触发 |
| DRAG_ENTER | node:dragenter | 拖拽对象进入节点时触发 |
| DRAG_OVER | node:dragover | 拖拽对象经过节点时触发 |
| DRAG_LEAVE | node:dragleave | 拖拽对象离开节点时触发 |
| DROP | node:drop | 拖拽对象在节点上放下时触发 |
边事件(EdgeEvent)
| 常量名 | 事件名 | 说明 |
|---|---|---|
| CLICK | edge:click | 点击边时触发 |
| DBLCLICK | edge:dblclick | 双击边时触发 |
| POINTER_OVER | edge:pointerover | 指针移入边时触发 |
| POINTER_LEAVE | edge:pointerleave | 指针移出边时触发 |
| POINTER_ENTER | edge:pointerenter | 指针移入边或其子元素时触发(不冒泡) |
| POINTER_MOVE | edge:pointermove | 指针在边上移动时触发 |
| POINTER_OUT | edge:pointerout | 指针移出边时触发 |
| POINTER_DOWN | edge:pointerdown | 指针在边上按下时触发 |
| POINTER_UP | edge:pointerup | 指针在边上抬起时触发 |
| CONTEXT_MENU | edge:contextmenu | 在边上打开右键菜单时触发 |
| DRAG_ENTER | edge:dragenter | 拖拽对象进入边时触发 |
| DRAG_OVER | edge:dragover | 拖拽对象经过边时触发 |
| DRAG_LEAVE | edge:dragleave | 拖拽对象离开边时触发 |
| DROP | edge:drop | 拖拽对象在边上放下时触发 |
Combo 事件(ComboEvent)
| 常量名 | 事件名 | 说明 |
|---|---|---|
| CLICK | combo:click | 点击 Combo 时触发 |
| DBLCLICK | combo:dblclick | 双击 Combo 时触发 |
| POINTER_OVER | combo:pointerover | 指针移入 Combo 时触发 |
| POINTER_LEAVE | combo:pointerleave | 指针移出 Combo 时触发 |
| POINTER_ENTER | combo:pointerenter | 指针移入 Combo 或其子元素时触发(不冒泡) |
| POINTER_MOVE | combo:pointermove | 指针在 Combo 上移动时触发 |
| POINTER_OUT | combo:pointerout | 指针移出 Combo 时触发 |
| POINTER_DOWN | combo:pointerdown | 指针在 Combo 上按下时触发 |
| POINTER_UP | combo:pointerup | 指针在 Combo 上抬起时触发 |
| CONTEXT_MENU | combo:contextmenu | 在 Combo 上打开右键菜单时触发 |
| DRAG_START | combo:dragstart | 开始拖拽 Combo 时触发 |
| DRAG | combo:drag | 拖拽 Combo 过程中触发 |
| DRAG_END | combo:dragend | 拖拽 Combo 结束时触发 |
| DRAG_ENTER | combo:dragenter | 拖拽对象进入 Combo 时触发 |
| DRAG_OVER | combo:dragover | 拖拽对象经过 Combo 时触发 |
| DRAG_LEAVE | combo:dragleave | 拖拽对象离开 Combo 时触发 |
| DROP | combo:drop | 拖拽对象在 Combo 上放下时触发 |
画布事件(CanvasEvent)
源码见 packages/g6/src/constants/events/canvas.ts:
| 常量名 | 事件名 | 说明 |
|---|---|---|
| CLICK | canvas:click | 点击画布空白区域时触发 |
| DBLCLICK | canvas:dblclick | 双击画布空白区域时触发 |
| POINTER_OVER | canvas:pointerover | 指针移入画布时触发 |
| POINTER_LEAVE | canvas:pointerleave | 指针移出画布时触发 |
| POINTER_ENTER | canvas:pointerenter | 指针移入画布或其子元素时触发(不冒泡) |
| POINTER_MOVE | canvas:pointermove | 指针在画布上移动时触发 |
| POINTER_OUT | canvas:pointerout | 指针移出画布时触发 |
| POINTER_DOWN | canvas:pointerdown | 指针在画布上按下时触发 |
| POINTER_UP | canvas:pointerup | 指针在画布上抬起时触发 |
| CONTEXT_MENU | canvas:contextmenu | 在画布上打开右键菜单时触发 |
| DRAG_START | canvas:dragstart | 开始拖拽画布时触发 |
| DRAG | canvas:drag | 拖拽画布过程中触发 |
| DRAG_END | canvas:dragend | 拖拽画布结束时触发 |
| DRAG_ENTER | canvas:dragenter | 拖拽对象进入画布时触发 |
| DRAG_OVER | canvas:dragover | 拖拽对象经过画布时触发 |
| DRAG_LEAVE | canvas:dragleave | 拖拽对象离开画布时触发 |
| DROP | canvas:drop | 拖拽对象在画布上放下时触发 |
| WHEEL | canvas:wheel | 在画布上滚动鼠标滚轮时触发 |
图生命周期事件(GraphEvent)
源码见 packages/g6/src/constants/events/graph.ts:
| 常量名 | 事件名 | 说明 |
|---|---|---|
| BEFORE_CANVAS_INIT | beforecanvasinit | 画布初始化之前触发 |
| AFTER_CANVAS_INIT | aftercanvasinit | 画布初始化之后触发 |
| BEFORE_SIZE_CHANGE | beforesizechange | 视口尺寸变更之前触发 |
| AFTER_SIZE_CHANGE | aftersizechange | 视口尺寸变更之后触发 |
| BEFORE_ELEMENT_CREATE | beforeelementcreate | 元素创建之前触发 |
| AFTER_ELEMENT_CREATE | afterelementcreate | 元素创建之后触发 |
| BEFORE_ELEMENT_UPDATE | beforeelementupdate | 元素更新之前触发 |
| AFTER_ELEMENT_UPDATE | afterelementupdate | 元素更新之后触发 |
| BEFORE_ELEMENT_DESTROY | beforeelementdestroy | 元素销毁之前触发 |
| AFTER_ELEMENT_DESTROY | afterelementdestroy | 元素销毁之后触发 |
| BEFORE_ELEMENT_TRANSLATE | beforeelementtranslate | 元素平移之前触发 |
| AFTER_ELEMENT_TRANSLATE | afterelementtranslate | 元素平移之后触发 |
| BEFORE_DRAW | beforedraw | 绘制开始之前触发 |
| AFTER_DRAW | afterdraw | 绘制结束之后触发 |
| BEFORE_RENDER | beforerender | 渲染开始之前触发 |
| AFTER_RENDER | afterrender | 渲染完成之后触发 |
| BEFORE_ANIMATE | beforeanimate | 动画开始之前触发 |
| AFTER_ANIMATE | afteranimate | 动画结束之后触发 |
| BEFORE_LAYOUT | beforelayout | 布局开始之前触发 |
| AFTER_LAYOUT | afterlayout | 布局结束之后触发 |
| BEFORE_STAGE_LAYOUT | beforestagelayout | 流水线布局中每个阶段执行前触发 |
| AFTER_STAGE_LAYOUT | afterstagelayout | 流水线布局中每个阶段执行后触发 |
| BEFORE_TRANSFORM | beforetransform | 视口变换之前触发 |
| AFTER_TRANSFORM | aftertransform | 视口变换之后触发 |
| BATCH_START | batchstart | 批处理开始时触发 |
| BATCH_END | batchend | 批处理结束时触发 |
| BEFORE_DESTROY | beforedestroy | 图销毁之前触发 |
| AFTER_DESTROY | afterdestroy | 图销毁之后触发 |
| BEFORE_RENDERER_CHANGE | beforerendererchange | 渲染器变更之前触发 |
| AFTER_RENDERER_CHANGE | afterrendererchange | 渲染器变更之后触发 |
这些生命周期事件在源码中被真实派发:例如render()中派发BEFORE_RENDER/AFTER_RENDER(packages/g6/src/runtime/graph.ts),布局控制器在 packages/g6/src/runtime/layout.ts 中派发BEFORE_LAYOUT/AFTER_LAYOUT/BEFORE_STAGE_LAYOUT/AFTER_STAGE_LAYOUT,视口控制器在 packages/g6/src/runtime/viewport.ts 中派发BEFORE_TRANSFORM/AFTER_TRANSFORM。
容器事件与通用事件
- ContainerEvent(容器事件):
keydown(按下键盘)、keyup(抬起键盘),与画布容器 DOM 绑定,由BehaviorController通过container.addEventListener转发(见 packages/g6/src/runtime/behavior.ts)。 - CommonEvent(通用事件):不带前缀、可监听全局的事件,如
click、dblclick、pointermove、wheel、pinch(多指捏合)等,完整枚举见 packages/g6/src/constants/events/common.ts。当事件命中某个元素时,G6 会同时派发带前缀事件(node:click)与不带前缀的通用事件(click),因此可以用CommonEvent.CLICK做事件委托(详见下文"事件委托")。
事件监听与解绑 API
G6 提供了以下四个事件相关 API:on、off、once、emit,其实现委托自@antv/event-emitter(见 packages/g6/src/runtime/graph.ts),且均返回this(Graph 实例),支持链式调用。
on:添加事件监听
const handler = (event) => { // 事件处理逻辑 }; graph.on('event_name', handler);带类型参数与 once 标志的完整签名:
on<T extends IEvent = IEvent>(eventName: string, callback: (event: T) => void, once?: boolean): this;一个结合 API 文档的实战示例(packages/site/docs/api/event.en.md):
import { NodeEvent, EdgeEvent, CanvasEvent } from '@antv/g6'; // 监听节点点击事件 graph.on(NodeEvent.CLICK, (evt) => { const { target } = evt; // 被点击节点的 id console.log(`Node ${target.id} was clicked`); // 获取节点数据 const nodeData = graph.getNodeData(target.id); console.log('Node data:', nodeData); // 修改节点状态 graph.setElementState(target.id, 'selected'); }); // 监听边 hover 事件 graph.on(EdgeEvent.POINTER_OVER, (evt) => { const { target } = evt; graph.setElementState(target.id, 'highlight'); }); // 监听画布拖拽事件 graph.on(CanvasEvent.DRAG, (evt) => { console.log('Canvas is being dragged'); });off:移除事件监听
graph.off('event_name', handler);当不传任何参数时,将移除所有事件监听:
graph.off();off有三种重载形式(见 packages/g6/src/runtime/graph.ts):
off()—— 移除全部事件监听;off(eventName)—— 移除指定事件的全部监听;off(eventName, callback)—— 移除指定事件的指定回调。
import { NodeEvent } from '@antv/g6'; // 移除所有节点点击事件监听 graph.off(NodeEvent.CLICK); // 移除指定回调 const handleNodeClick = (evt) => { console.log('Node clicked:', evt.target.id); }; graph.on(NodeEvent.CLICK, handleNodeClick); // 在合适时机移除该监听 graph.off(NodeEvent.CLICK, handleNodeClick);once:一次性事件监听
添加一次性事件监听,事件触发后监听器会自动移除:
graph.once('event_name', handler);适合"首次渲染后做一次性初始化"、"等待用户首次交互"等场景:
import { GraphEvent, NodeEvent } from '@antv/g6'; // 首次渲染完成后只执行一次 graph.once(GraphEvent.AFTER_RENDER, () => { console.log('Chart rendered for the first time'); highlightImportantNodes(); }); // 用户首次点击节点后给出引导提示 graph.once(NodeEvent.CLICK, (evt) => { console.log('User clicked a node for the first time:', evt.target.id); showTutorialTip('You can drag nodes to change their position'); });emit:手动触发事件
如果需要手动触发事件,可以使用emit方法:
graph.emit('event_name', { // 事件数据 });emit的典型用途是测试与状态模拟:G6 官方测试用例 packages/g6/tests/unit/behaviors/click-select.spec.ts 中正是通过graph.emit(NodeEvent.CLICK, { target: { id: '0' }, targetType: 'node' })来模拟节点点击,从而驱动click-select行为进行快照断言。这说明事件对象至少需要携带target与targetType字段才能被下游逻辑正确识别。
事件对象结构
大多数事件回调会收到一个事件对象,其中包含以下常用属性(类型定义见 packages/g6/src/types/event.ts):
| 属性 | 说明 |
|---|---|
target | 触发事件的元素(节点 / 边 / Combo / 画布) |
targetType | 触发事件的元素类型:'node' \| 'edge' \| 'combo' \| 'canvas' |
originalTarget | 触发事件的原始图形(通常是元素内部的某个 shape) |
currentTarget | 当前触发事件的对象 |
originalEvent | 原始浏览器事件对象 |
事件对象的类型体系为IEvent联合类型,包含IGraphLifeCycleEvent、IAnimateEvent、IElementLifeCycleEvent、IViewportEvent、IPointerEvent、IWheelEvent、IKeyboardEvent、IDragEvent。其中拖拽类事件额外携带dx、dy(本次拖拽的位移增量);IPointerEvent基于 G 的FederatedPointerEvent扩展,保留了原生指针事件的全部字段。
结合这些属性可以精确控制交互行为:
import { CommonEvent } from '@antv/g6'; // 统一处理所有元素的点击事件 graph.on(CommonEvent.CLICK, (evt) => { const { targetType, target } = evt; if (targetType === 'node') { console.log('Clicked on node:', target.id); } else if (targetType === 'edge') { console.log('Clicked on edge:', target.id); } else { console.log('Clicked on canvas blank area'); } });底层原理:事件是如何分发到 Graph 上的
理解事件系统的底层分发机制有助于排查问题。从源码看,浏览器原生事件到 G6 事件的流转由BehaviorController完成,关键实现在 packages/g6/src/runtime/behavior.ts:
- 注册原生监听:
forwardEvents()(第 33-66 行)在画布容器上监听keydown/keyup(通过forwardContainerEvents直接转发),并在画布的document上监听click、dblclick、pointerover、pointermove、drag、drop、wheel等一整套通用事件。 - 解析事件目标:
forwardCanvasEvents(第 68-126 行)调用工具函数eventTargetOf命中事件目标。该函数定义在 packages/g6/src/utils/event/index.ts,它从事件命中的图形(shape)沿parentElement向上遍历,判断是否属于节点 / 边 / Combo(分别调用isNode/isEdge/isCombo判断),返回{ type: 'node' | 'edge' | 'combo' | 'canvas', element }。 - 带前缀派发:G6 将事件同时派发为带前缀事件与通用事件,即
graph.emit(${targetType}:${type}, stdEvent)与graph.emit(type, stdEvent)(第 102-103 行)。这正是graph.on(NodeEvent.CLICK)与graph.on(CommonEvent.CLICK)都能收到点击事件的原因。双击(detail === 2)和右键(button === 2)也会被特殊处理为dblclick与contextmenu事件(第 106-125 行)。 - 容器的键盘事件:
forwardContainerEvents(第 128-130 行)直接把keydown/keyup转发为ContainerEvent.KEY_DOWN/KEY_UP。 - 生命周期事件:图生命周期类事件则由各运行时控制器(graph / layout / viewport 等)通过
emit(graph, new GraphLifeCycleEvent(...))主动派发,事件载体类定义在 packages/g6/src/utils/event/events.ts,其中GraphLifeCycleEvent、AnimateEvent、ElementLifeCycleEvent、ViewportEvent均继承自BaseEvent。
实战技巧
链式调用
由于on/once/off都返回 Graph 实例,可以连续注册多个事件:
import { NodeEvent, EdgeEvent, CanvasEvent } from '@antv/g6'; graph .on(NodeEvent.CLICK, handleNodeClick) .on(EdgeEvent.CLICK, handleEdgeClick) .on(CanvasEvent.WHEEL, handleCanvasZoom);事件委托
利用事件冒泡机制,可以在父级统一处理子元素事件(G6 默认同时派发通用事件):
import { CommonEvent } from '@antv/g6'; graph.on(CommonEvent.CLICK, (evt) => { const { targetType, target } = evt; if (targetType === 'node') { console.log('Clicked on node:', target.id); } else if (targetType === 'edge') { console.log('Clicked on edge:', target.id); } else { console.log('Clicked on canvas blank area'); } });插件中的事件生命周期管理
G6 内置插件大量使用graph.on/graph.off完成事件绑定与解绑。以 Tooltip 插件 packages/g6/src/plugins/tooltip.ts 为例:它根据trigger配置('hover'或'click')动态生成事件映射表getEvents(),在bindEvents()中逐个graph.on(eventName, handler),在unbindEvents()/update()时逐个graph.off(eventName, handler)以避免重复绑定。这种"绑定前先解绑"的模式值得在自定义逻辑中借鉴——尤其是在动态切换监听行为时,务必成对使用on/off,防止内存泄漏与重复回调。
使用常量枚举而非字符串
所有事件都应优先使用GraphEvent、CanvasEvent、NodeEvent、EdgeEvent、ComboEvent、CommonEvent、ContainerEvent等枚举常量,既可获得类型检查与 IDE 提示,也能规避字符串拼写错误。
小结
G6 的事件系统以 G 图形引擎为底座,通过统一的事件常量枚举(packages/g6/src/constants/events/)、Graph 实例上的on/once/off/emitAPI 以及BehaviorController的底层事件转发,将图生命周期、画布交互与元素交互三类事件完整地暴露给开发者。掌握本文的事件分类、常量清单、事件对象结构与分发原理,即可在 G6 项目中实现从"点击节点改状态"到"渲染完成后初始化"的各类交互逻辑。更完整的 API 参数与逐事件说明,可继续查阅 packages/site/docs/api/event.en.md。
- 数据可视化
- 前端
- 图表库
【免费下载链接】G6
♾ A Graph Visualization Framework in JavaScript.
相关推荐
canvas-editor事件系统完全解析:从基础监听到底层原理
canvas editor事件系统完全解析:从基础监听到底层原理 canvas editor作为一款基于Canvas/SVG的富文本编辑器,其强大而灵活的事件系
前端UI组件富文本终极Cloudreve插件钩子开发指南:从事件监听到底层实现的完整教程
终极Cloudreve插件钩子开发指南:从事件监听到底层实现的完整教程 Cloudreve是一款功能强大的自托管云盘系统,支持多种存储提供商。本文将带你深入了解
后端对象存储G6 事件系统完全指南:事件监听 API 与常量枚举实战
G6 事件系统完全指南:事件监听 API 与常量枚举实战 G6 为 JavaScript 图可视化应用提供了完整的事件机制,支持响应节点点击、边悬停、画布拖拽等
数据可视化前端图表库
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考