mcpField
P
pixijs-events

pixijs-events

github.com/pixijs开发3586.0k 次安装4 周前更新
代码仓库

关于 pixijs-events

面向 PixiJS 开发者的场景交互技能,介绍事件模式、指针处理、传播、命中测试、光标和性能控制。

主要功能

  1. 01配置事件模式和监听器
  2. 02涵盖指针、鼠标、触摸及生命周期事件
  3. 03讲解事件传播与捕获监听器
  4. 04设置自定义命中区域和光标样式
  5. 05提供事件性能优化建议

适用场景

  1. 01处理 UI 元素的点击
  2. 02实现拖动交互
  3. 03管理嵌套场景中的事件传播
  4. 04减少大型场景的命中测试开销

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/pixijs/pixijs-skills --skill pixijs-events

安装后在 Claude Code 中输入/pixijs-events确认已加载。

详细介绍

PixiJS 的联邦事件系统会在场景图中模拟 DOM 事件。设置 container.eventMode = 'static' 即可让对象接收事件,然后使用 .on()、addEventListener() 或 onEventName 属性处理程序进行监听。移动事件只会在指针经过正在监听的对象时触发;拖动时请使用 globalpointermove。

快速入门

const button = new Sprite(await Assets.load("button.png"));
button.eventMode = "static";
button.cursor = "pointer";
app.stage.addChild(button);

button.on("pointertap", (event) => {
  console.log("clicked at", event.global.x, event.global.y);
});

let dragging = false;
button.on("pointerdown", () => {
  dragging = true;
});
button.on("pointerup", () => {
  dragging = false;
});
button.on("pointerupoutside", () => {
  dragging = false;
});
button.on("globalpointermove", (event) => {
  if (dragging) button.parent.toLocal(event.global, undefined, button.position);
});

相关技能: pixijs-accessibility(屏幕阅读器 + 键盘)、pixijs-scene-dom-container(HTML 覆盖层)、pixijs-performance(事件密集型场景)。

核心模式

eventMode 的取值
import { Sprite } from "pixi.js";

const sprite = new Sprite();

// No interaction at all; children also ignored
sprite.eventMode = "none";

// Default. Self not interactive; interactive children still work
sprite.eventMode = "passive";

// Hit tested only when a parent is interactive
sprite.eventMode = "auto";

// Standard interaction: receives pointer/mouse/touch events
sprite.eventMode = "static";

// Like static, but also fires synthetic events from the ticker
// when the pointer is stationary (for animated objects under cursor)
sprite.eventMode = "dynamic";

按钮、UI 元素和拖动目标使用 'static'。只有当对象会在静止的光标下移动,且需要持续更新悬停状态时,才使用 'dynamic'。

使用 isInteractive() 检查对象是否可以接收事件:

sprite.eventMode = "static";
sprite.isInteractive(); // true

sprite.eventMode = "passive";
sprite.isInteractive(); // false
事件类型

指针事件(推荐,兼容多种设备):pointerdown、pointerup、pointerupoutside、pointermove、pointerover、pointerout、pointerenter、pointerleave、pointertap、pointercancel。

鼠标事件:mousedown、mouseup、mouseupoutside、mousemove、mouseover、mouseout、mouseenter、mouseleave、click、rightdown、rightup、rightupoutside、rightclick、contextmenu、wheel。PixiJS 不会阻止浏览器显示上下文菜单;在 contextmenu 监听器中调用 event.preventDefault(),即可阻止菜单显示。取消 rightdown、rightup 或 rightclick 不会阻止菜单显示。在 macOS 上,按住 ctrl 并单击也会触发 contextmenu,而浏览器可能会将其报告为左键单击。

触摸事件:touchstart、touchend、touchendoutside、touchmove、touchcancel、tap。每次触摸都会携带从原生 TouchEvent 复制而来的 altKey、ctrlKey、metaKey 和 shiftKey,因此修饰键的工作方式与鼠标或指针事件相同。

全局移动事件:globalpointermove、globalmousemove、globaltouchmove。无论指针是否经过正在监听的对象,这些事件都会在每次指针移动时触发。

容器生命周期事件(无需设置 eventMode):added、removed、destroyed、childAdded、childRemoved、visibleChanged。

监听方式
import { Sprite } from "pixi.js";

const sprite = new Sprite();
sprite.eventMode = "static";

// EventEmitter style (recommended)
const handler = (e) => console.log("clicked");
sprite.on("pointerdown", handler);
sprite.once("pointerdown", handler); // one-time
sprite.off("pointerdown", handler);

// DOM style
sprite.addEventListener(
  "click",
  (event) => {
    console.log("Clicked!", event.detail);
  },
  { once: true },
);

// Property-based handlers
sprite.onclick = (event) => {
  console.log("Clicked!", event.detail);
};
指针事件与传播
import { Sprite, Container } from "pixi.js";

const parent = new Container();
parent.eventMode = "static";

const child = new Sprite();
child.eventMode = "static";
parent.addChild(child);

child.on("pointerdown", (event) => {
  console.log("child pressed");
  event.stopPropagation(); // prevent parent from receiving this event
});

parent.on("pointerdown", () => {
  console.log("parent pressed (only if child did not stop propagation)");
});
捕获阶段事件

所有事件都支持捕获阶段,只需在事件名称后追加 capture(例如 pointerdowncapture、clickcapture)。捕获阶段监听器会在事件到达目标之前触发。

container.addEventListener(
  "pointerdown",
  (event) => {
    event.stopImmediatePropagation(); // blocks event from reaching children
  },
  { capture: true },
);
命中测试

指针事件触发时,PixiJS 会遍历显示树,查找指针下方最顶层的交互元素。遍历遵循以下规则:

  • 容器上的 eventMode = 'none' 会跳过该元素及其整个子树。
  • 容器上的 interactiveChildren = false 会跳过其子元素(但仍可测试容器本身)。
  • hitArea 会覆盖基于边界的测试;只会检查该形状。
  • 不可见、不可渲染或不可测量的对象会被跳过。

设置自定义 hitArea 可覆盖基于边界的测试。通过减少需要检查的几何图形,这也能加快大型或复杂对象的命中测试:

import { Sprite, Rectangle, Circle, Polygon } from "pixi.js";

const sprite = new Sprite();
sprite.eventMode = "static";

// Rectangular hit area
sprite.hitArea = new Rectangle(0, 0, 100, 50);

// Circular hit area
sprite.hitArea = new Circle(50, 50, 40);

// Polygon hit area
sprite.hitArea = new Polygon([0, 0, 100, 0, 50, 100]);

// Custom hit test via contains()
sprite.hitArea = {
  contains(x: number, y: number): boolean {
    return x >= 0 && x <= 100 && y >= 0 && y <= 100;
  },
};
全局移动事件与拖动
import { Sprite, FederatedPointerEvent } from "pixi.js";

const sprite = new Sprite();
sprite.eventMode = "static";
sprite.cursor = "grab";

let dragging = false;

sprite.on("pointerdown", (event: FederatedPointerEvent) => {
  dragging = true;
  sprite.cursor = "grabbing";
});

// globalpointermove fires even when pointer leaves the object
sprite.on("globalpointermove", (event: FederatedPointerEvent) => {
  if (dragging) {
    sprite.position.set(event.global.x, event.global.y);
  }
});

sprite.on("pointerup", () => {
  dragging = false;
  sprite.cursor = "grab";
});

sprite.on("pointerupoutside", () => {
  dragging = false;
  sprite.cursor = "grab";
});
光标样式

基本用法是为每个对象设置 cursor 属性。对于可复用的光标,可在事件系统中注册命名样式:

app.renderer.events.cursorStyles.default = "url('bunny.png'), auto";
app.renderer.events.cursorStyles.hover = "url('bunny_saturated.png'), auto";

sprite.eventMode = "static";
sprite.cursor = "hover"; // uses the registered 'hover' style

光标样式可以是字符串(CSS 光标值)、对象(用作 CSS 样式)或函数(使用模式字符串调用)。

事件属性

FederatedPointerEvent 携带丰富的输入数据;常用字段包括:

sprite.on("pointerdown", (event: FederatedPointerEvent) => {
  event.global; // scene-space Point where the event happened
  event.client; // CSS-pixel Point relative to the viewport
  event.offset; // Point w.r.t. target Container in world space (not supported at the moment)
  event.target; // the Container that received the event
  event.currentTarget; // the Container whose listener is running

  event.pointerType; // 'mouse' | 'pen' | 'touch'
  event.pointerId; // unique id for multi-touch tracking
  event.isPrimary; // first pointer in a multi-pointer gesture
  event.persistentDeviceId; // always 0; present for DOM PointerEvent type compatibility
  event.pressure; // 0-1 pen/touch pressure
  event.button; // 0 left, 1 middle, 2 right
  event.buttons; // bitmask of held buttons
  event.altKey; // modifier key state
  event.ctrlKey;
  event.shiftKey;
  event.metaKey;

  event.nativeEvent; // the underlying DOM PointerEvent / MouseEvent / Touch
  event.preventDefault();
  event.stopPropagation();
  event.stopImmediatePropagation();
});

FederatedWheelEvent 还包含 deltaX、deltaY、deltaZ 和 deltaMode。滚轮事件会在与指针事件相同的命中测试对象上触发。

事件功能

全局切换事件类别以提升性能:

await app.init({
  eventFeatures: {
    move: true, // pointer/mouse/touch move events
    globalMove: true, // global move events (globalpointermove, etc.)
    click: true, // click/tap/press and contextmenu events
    wheel: true, // mouse wheel events
  },
});

// or configure after init
app.renderer.events.features.globalMove = false;
性能提示
  • 在非交互子树上设置 eventMode = 'none',以完全跳过命中测试。
  • 对仅容器本身需要交互的容器设置 interactiveChildren = false。
  • 在大型或复杂对象上使用 hitArea,以便用低开销的形状检测替代基于边界的命中测试。
  • 静止元素优先使用 'static';仅对在静止指针下移动或播放动画的对象使用 'dynamic'。
  • 通过 eventFeatures 禁用未使用的事件功能(例如 globalMove: false),以减少每帧的工作量。

常见错误

[HIGH] 默认 eventMode 为 passive

错误:

const sprite = new Sprite(texture);
sprite.on("pointerdown", () => {
  console.log("clicked");
});

正确:

const sprite = new Sprite(texture);
sprite.eventMode = "static";
sprite.on("pointerdown", () => {
  console.log("clicked");
});

默认的 eventMode 为 'passive',这意味着对象本身不会接收任何事件。在任何监听器触发之前,必须显式将 eventMode 设置为 'static' 或 'dynamic'。

[HIGH] buttonMode 已移除;请使用 cursor

错误:

sprite.interactive = true;
sprite.buttonMode = true;

正确:

sprite.eventMode = "static";
sprite.cursor = "pointer";

buttonMode 已在 v8 中移除。使用 cursor = 'pointer' 可在悬停时显示手形光标。interactive = true 仍可作为 eventMode = 'static' 的别名使用,但更推荐使用 eventMode。

[HIGH] 在 v8 中,移动事件仅在指针位于对象上方时触发

错误:

sprite.eventMode = "static";
sprite.on("pointermove", (event) => {
  // expects to fire everywhere; only fires inside sprite bounds
  updateDrag(event.global.x, event.global.y);
});

正确:

sprite.eventMode = "static";
sprite.on("globalpointermove", (event) => {
  // fires everywhere, even outside sprite bounds
  updateDrag(event.global.x, event.global.y);
});

在 v8 中,pointermove、mousemove 和 touchmove 仅在指针位于显示对象上方时触发。在 v7 中,它们会在画布上的任意位置移动时触发。对于拖动操作或全局追踪,请使用 globalpointermove、globalmousemove 或 globaltouchmove。

[MEDIUM] cursor 不会从父级继承

在父容器上设置 cursor 对其子级没有影响。只有直接命中的目标的 cursor 值会生效。

// This does NOT make children show a pointer cursor
parent.cursor = "pointer";

// Each interactive child needs its own cursor
child.eventMode = "static";
child.cursor = "pointer";

如果希望所有子级使用统一的光标,请分别在每个可交互子级上设置 cursor,或者在父级上设置 hitArea 并将子级设为非交互。

API 参考

评论

支持 Markdown