模块5: 事件系统
DOM Event 兼容的图形事件模型
1. 事件系统概述
@antv/g 实现了与浏览器 DOM Event API 完全兼容的事件系统。你可以用 addEventListener、事件冒泡/捕获等标准方式处理图形交互。
核心设计借鉴了 Pixi.js 的 FederatedEvent(联邦事件)系统,在 Canvas 上模拟完整的 DOM 事件传播机制。
2. EventTarget 基类
// 所有图形节点都继承 EventTarget circle.addEventListener('click', (e) => { // e 是 FederatedPointerEvent // e.target - 被点击的图形 // e.currentTarget - 绑定事件的图形 }); // 移除事件 circle.removeEventListener('click', handler); // 触发自定义事件 circle.dispatchEvent(new CustomEvent('myevent', { detail: { data: 42 } })); // 便捷别名 circle.on('click', handler); circle.off('click', handler);
3. 事件传播机制
与 DOM 事件一样,分三个阶段:
捕获阶段 (Capture) 目标阶段 冒泡阶段 (Bubble) │ │ │ Canvas Circle Canvas │ │ ↑ Group [handler] Group │ ↑ Circle ──────────────────────────────────────── Circle
// 在捕获阶段监听 group.addEventListener('click', handler, true); // 或 group.addEventListener('click', handler, { capture: true }); // 阻止冒泡 circle.addEventListener('click', (e) => { e.stopPropagation(); }); // 阻止默认行为 circle.addEventListener('click', (e) => { e.preventDefault(); });
4. 支持的事件类型
4.1 指针事件 (PointerEvent)
| 事件 | 说明 |
|---|---|
pointerdown | 按下 |
pointerup | 释放 |
pointermove | 移动 |
pointerover | 进入(冒泡) |
pointerout | 离开(冒泡) |
pointerenter | 进入(不冒泡) |
pointerleave | 离开(不冒泡) |
4.2 鼠标事件
| 事件 | 说明 |
|---|---|
click | 点击 |
dblclick | 双击 |
mousedown / mouseup | 按下/释放 |
mousemove | 移动 |
mouseover / mouseout | 进入/离开(冒泡) |
mouseenter / mouseleave | 进入/离开(不冒泡) |
wheel | 滚轮 |
contextmenu | 右键菜单 |
5. FederatedEvent 联邦事件
@antv/g 将浏览器原生事件转换为 FederatedEvent,在场景图上进行传播:
// FederatedEvent 关键属性 class FederatedEvent { type: string; // 事件类型 target: DisplayObject; // 事件目标 currentTarget: DisplayObject; // 当前处理者 bubbles: boolean; // 是否冒泡 eventPhase: number; // 0=NONE 1=CAPTURE 2=TARGET 3=BUBBLE timeStamp: number; // 时间戳 nativeEvent: Event; // 原生浏览器事件 }
6. 拾取机制 (Hit Testing)
当鼠标/触摸事件发生时,需要判断点击了哪个图形。不同渲染器使用不同的拾取策略:
| 渲染器 | 拾取方式 | 说明 |
|---|---|---|
| Canvas2D | 几何算法 / isPointInPath | 重建路径后使用 Canvas API 判断 |
| SVG | DOM 原生 | SVG 元素天然支持事件 |
| WebGL/WebGPU | GPU Picking / 射线检测 | 颜色编码或射线交叉 |
// pointerEvents 控制拾取行为 circle.style.pointerEvents = 'auto'; // 默认,可被拾取 circle.style.pointerEvents = 'none'; // 不可被拾取,事件穿透 circle.style.pointerEvents = 'stroke'; // 仅描边可被拾取 circle.style.pointerEvents = 'fill'; // 仅填充区域可被拾取
7. EventService 事件服务
位于 packages/g-lite/src/services/EventService.ts,负责:
- 监听 Canvas DOM 元素的原生事件
- 执行拾取(Hit Test)确定目标图形
- 构造 FederatedEvent
- 沿场景图传播事件(捕获→目标→冒泡)
- 维护 pointer over/out 状态(用于 mouseenter/leave)
8. 事件委托
// 在父容器上监听,利用冒泡处理所有子节点事件 group.addEventListener('click', (e) => { const target = e.target; // 实际被点击的子图形 if (target.id === 'delete-btn') { target.parentNode.removeChild(target); } });
9. 交互示例
// 拖拽效果 let dragging = false; let startX, startY; circle.addEventListener('pointerdown', (e) => { dragging = true; startX = e.clientX - circle.getPosition()[0]; startY = e.clientY - circle.getPosition()[1]; circle.style.cursor = 'grabbing'; }); canvas.addEventListener('pointermove', (e) => { if (!dragging) return; circle.setPosition(e.clientX - startX, e.clientY - startY); }); canvas.addEventListener('pointerup', () => { dragging = false; circle.style.cursor = 'grab'; });