模块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 判断
SVGDOM 原生SVG 元素天然支持事件
WebGL/WebGPUGPU 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,负责:

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';
});