G6 事件系统完全指南:从 Graph/Canvas/Element 事件监听到底层分发机制
2026/9/24 0:36:16 网站建设 项目流程
  • 数据可视化
  • 前端
  • 图表库

【免费下载链接】G6

♾ A Graph Visualization Framework in JavaScript.

项目地址:https://gitcode.com/gh_mirrors/g6/G6
点击查看免费下载

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 对外导出CanvasEventComboEventCommonEventContainerEventEdgeEventGraphEventHistoryEventNodeEvent等枚举,开发者可以从@antv/g6直接引入使用。
  • 统一的监听 APIGraph实例继承自EventEmitter(见 packages/g6/src/runtime/graph.ts),on/once/off/emit四个方法在 Graph 上对外暴露,实际委托给@antv/event-emitter完成底层的事件注册与派发。

事件分类:Graph、Canvas 与 Element

G6 的事件类型主要分为以下三大类:

  1. Graph 事件(图实例事件):与整个图实例生命周期相关的事件,例如渲染完成事件、更新事件等;
  2. Canvas 事件(画布事件):与画布相关的事件,例如画布点击、画布拖拽等;
  3. 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.tsedge.tscombo.tscanvas.tsgraph.tscommon.tscontainer.tshistory.tsanimation.ts),官方 API 文档强烈推荐使用这些常量而非直接书写字符串事件名,其优势在于:

  • 类型安全:避免事件名字符串拼写错误;
  • 智能提示:在 IDE 中可获得自动补全与代码提示。

节点事件(NodeEvent)

源码见 packages/g6/src/constants/events/node.ts:

常量名事件名说明
CLICKnode:click点击节点时触发
DBLCLICKnode:dblclick双击节点时触发
POINTER_OVERnode:pointerover指针移入节点时触发
POINTER_LEAVEnode:pointerleave指针移出节点时触发
POINTER_ENTERnode:pointerenter指针移入节点或其子元素时触发(不冒泡)
POINTER_MOVEnode:pointermove指针在节点上移动时触发
POINTER_OUTnode:pointerout指针移出节点时触发
POINTER_DOWNnode:pointerdown指针在节点上按下时触发
POINTER_UPnode:pointerup指针在节点上抬起时触发
CONTEXT_MENUnode:contextmenu在节点上打开右键菜单时触发
DRAG_STARTnode:dragstart开始拖拽节点时触发
DRAGnode:drag拖拽节点过程中触发
DRAG_ENDnode:dragend拖拽节点结束时触发
DRAG_ENTERnode:dragenter拖拽对象进入节点时触发
DRAG_OVERnode:dragover拖拽对象经过节点时触发
DRAG_LEAVEnode:dragleave拖拽对象离开节点时触发
DROPnode:drop拖拽对象在节点上放下时触发

边事件(EdgeEvent)

常量名事件名说明
CLICKedge:click点击边时触发
DBLCLICKedge:dblclick双击边时触发
POINTER_OVERedge:pointerover指针移入边时触发
POINTER_LEAVEedge:pointerleave指针移出边时触发
POINTER_ENTERedge:pointerenter指针移入边或其子元素时触发(不冒泡)
POINTER_MOVEedge:pointermove指针在边上移动时触发
POINTER_OUTedge:pointerout指针移出边时触发
POINTER_DOWNedge:pointerdown指针在边上按下时触发
POINTER_UPedge:pointerup指针在边上抬起时触发
CONTEXT_MENUedge:contextmenu在边上打开右键菜单时触发
DRAG_ENTERedge:dragenter拖拽对象进入边时触发
DRAG_OVERedge:dragover拖拽对象经过边时触发
DRAG_LEAVEedge:dragleave拖拽对象离开边时触发
DROPedge:drop拖拽对象在边上放下时触发

Combo 事件(ComboEvent)

常量名事件名说明
CLICKcombo:click点击 Combo 时触发
DBLCLICKcombo:dblclick双击 Combo 时触发
POINTER_OVERcombo:pointerover指针移入 Combo 时触发
POINTER_LEAVEcombo:pointerleave指针移出 Combo 时触发
POINTER_ENTERcombo:pointerenter指针移入 Combo 或其子元素时触发(不冒泡)
POINTER_MOVEcombo:pointermove指针在 Combo 上移动时触发
POINTER_OUTcombo:pointerout指针移出 Combo 时触发
POINTER_DOWNcombo:pointerdown指针在 Combo 上按下时触发
POINTER_UPcombo:pointerup指针在 Combo 上抬起时触发
CONTEXT_MENUcombo:contextmenu在 Combo 上打开右键菜单时触发
DRAG_STARTcombo:dragstart开始拖拽 Combo 时触发
DRAGcombo:drag拖拽 Combo 过程中触发
DRAG_ENDcombo:dragend拖拽 Combo 结束时触发
DRAG_ENTERcombo:dragenter拖拽对象进入 Combo 时触发
DRAG_OVERcombo:dragover拖拽对象经过 Combo 时触发
DRAG_LEAVEcombo:dragleave拖拽对象离开 Combo 时触发
DROPcombo:drop拖拽对象在 Combo 上放下时触发

画布事件(CanvasEvent)

源码见 packages/g6/src/constants/events/canvas.ts:

常量名事件名说明
CLICKcanvas:click点击画布空白区域时触发
DBLCLICKcanvas:dblclick双击画布空白区域时触发
POINTER_OVERcanvas:pointerover指针移入画布时触发
POINTER_LEAVEcanvas:pointerleave指针移出画布时触发
POINTER_ENTERcanvas:pointerenter指针移入画布或其子元素时触发(不冒泡)
POINTER_MOVEcanvas:pointermove指针在画布上移动时触发
POINTER_OUTcanvas:pointerout指针移出画布时触发
POINTER_DOWNcanvas:pointerdown指针在画布上按下时触发
POINTER_UPcanvas:pointerup指针在画布上抬起时触发
CONTEXT_MENUcanvas:contextmenu在画布上打开右键菜单时触发
DRAG_STARTcanvas:dragstart开始拖拽画布时触发
DRAGcanvas:drag拖拽画布过程中触发
DRAG_ENDcanvas:dragend拖拽画布结束时触发
DRAG_ENTERcanvas:dragenter拖拽对象进入画布时触发
DRAG_OVERcanvas:dragover拖拽对象经过画布时触发
DRAG_LEAVEcanvas:dragleave拖拽对象离开画布时触发
DROPcanvas:drop拖拽对象在画布上放下时触发
WHEELcanvas:wheel在画布上滚动鼠标滚轮时触发

图生命周期事件(GraphEvent)

源码见 packages/g6/src/constants/events/graph.ts:

常量名事件名说明
BEFORE_CANVAS_INITbeforecanvasinit画布初始化之前触发
AFTER_CANVAS_INITaftercanvasinit画布初始化之后触发
BEFORE_SIZE_CHANGEbeforesizechange视口尺寸变更之前触发
AFTER_SIZE_CHANGEaftersizechange视口尺寸变更之后触发
BEFORE_ELEMENT_CREATEbeforeelementcreate元素创建之前触发
AFTER_ELEMENT_CREATEafterelementcreate元素创建之后触发
BEFORE_ELEMENT_UPDATEbeforeelementupdate元素更新之前触发
AFTER_ELEMENT_UPDATEafterelementupdate元素更新之后触发
BEFORE_ELEMENT_DESTROYbeforeelementdestroy元素销毁之前触发
AFTER_ELEMENT_DESTROYafterelementdestroy元素销毁之后触发
BEFORE_ELEMENT_TRANSLATEbeforeelementtranslate元素平移之前触发
AFTER_ELEMENT_TRANSLATEafterelementtranslate元素平移之后触发
BEFORE_DRAWbeforedraw绘制开始之前触发
AFTER_DRAWafterdraw绘制结束之后触发
BEFORE_RENDERbeforerender渲染开始之前触发
AFTER_RENDERafterrender渲染完成之后触发
BEFORE_ANIMATEbeforeanimate动画开始之前触发
AFTER_ANIMATEafteranimate动画结束之后触发
BEFORE_LAYOUTbeforelayout布局开始之前触发
AFTER_LAYOUTafterlayout布局结束之后触发
BEFORE_STAGE_LAYOUTbeforestagelayout流水线布局中每个阶段执行前触发
AFTER_STAGE_LAYOUTafterstagelayout流水线布局中每个阶段执行后触发
BEFORE_TRANSFORMbeforetransform视口变换之前触发
AFTER_TRANSFORMaftertransform视口变换之后触发
BATCH_STARTbatchstart批处理开始时触发
BATCH_ENDbatchend批处理结束时触发
BEFORE_DESTROYbeforedestroy图销毁之前触发
AFTER_DESTROYafterdestroy图销毁之后触发
BEFORE_RENDERER_CHANGEbeforerendererchange渲染器变更之前触发
AFTER_RENDERER_CHANGEafterrendererchange渲染器变更之后触发

这些生命周期事件在源码中被真实派发:例如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(通用事件):不带前缀、可监听全局的事件,如clickdblclickpointermovewheelpinch(多指捏合)等,完整枚举见 packages/g6/src/constants/events/common.ts。当事件命中某个元素时,G6 会同时派发带前缀事件(node:click)与不带前缀的通用事件(click),因此可以用CommonEvent.CLICK做事件委托(详见下文"事件委托")。

事件监听与解绑 API

G6 提供了以下四个事件相关 API:onoffonceemit,其实现委托自@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):

  1. off()—— 移除全部事件监听;
  2. off(eventName)—— 移除指定事件的全部监听;
  3. 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行为进行快照断言。这说明事件对象至少需要携带targettargetType字段才能被下游逻辑正确识别。

事件对象结构

大多数事件回调会收到一个事件对象,其中包含以下常用属性(类型定义见 packages/g6/src/types/event.ts):

属性说明
target触发事件的元素(节点 / 边 / Combo / 画布)
targetType触发事件的元素类型:'node' \| 'edge' \| 'combo' \| 'canvas'
originalTarget触发事件的原始图形(通常是元素内部的某个 shape)
currentTarget当前触发事件的对象
originalEvent原始浏览器事件对象

事件对象的类型体系为IEvent联合类型,包含IGraphLifeCycleEventIAnimateEventIElementLifeCycleEventIViewportEventIPointerEventIWheelEventIKeyboardEventIDragEvent。其中拖拽类事件额外携带dxdy(本次拖拽的位移增量);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:

  1. 注册原生监听forwardEvents()(第 33-66 行)在画布容器上监听keydown/keyup(通过forwardContainerEvents直接转发),并在画布的document上监听clickdblclickpointeroverpointermovedragdropwheel等一整套通用事件。
  2. 解析事件目标forwardCanvasEvents(第 68-126 行)调用工具函数eventTargetOf命中事件目标。该函数定义在 packages/g6/src/utils/event/index.ts,它从事件命中的图形(shape)沿parentElement向上遍历,判断是否属于节点 / 边 / Combo(分别调用isNode/isEdge/isCombo判断),返回{ type: 'node' | 'edge' | 'combo' | 'canvas', element }
  3. 带前缀派发:G6 将事件同时派发为带前缀事件与通用事件,即graph.emit(${targetType}:${type}, stdEvent)graph.emit(type, stdEvent)(第 102-103 行)。这正是graph.on(NodeEvent.CLICK)graph.on(CommonEvent.CLICK)都能收到点击事件的原因。双击(detail === 2)和右键(button === 2)也会被特殊处理为dblclickcontextmenu事件(第 106-125 行)。
  4. 容器的键盘事件forwardContainerEvents(第 128-130 行)直接把keydown/keyup转发为ContainerEvent.KEY_DOWN/KEY_UP
  5. 生命周期事件:图生命周期类事件则由各运行时控制器(graph / layout / viewport 等)通过emit(graph, new GraphLifeCycleEvent(...))主动派发,事件载体类定义在 packages/g6/src/utils/event/events.ts,其中GraphLifeCycleEventAnimateEventElementLifeCycleEventViewportEvent均继承自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,防止内存泄漏与重复回调。

使用常量枚举而非字符串

所有事件都应优先使用GraphEventCanvasEventNodeEventEdgeEventComboEventCommonEventContainerEvent等枚举常量,既可获得类型检查与 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.

项目地址:https://gitcode.com/gh_mirrors/g6/G6
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询