pixijs-events

Par pixijs · pixijs-skills

Utilisez cette skill pour gérer les entrées pointeur, souris, tactile ou molette dans PixiJS v8. Couvre `eventMode` (`none`, `passive`, `auto`, `static`, `dynamic`), les types `FederatedEvent`, la propagation et la phase de capture, `hitArea`, `interactiveChildren`, `cursor` et `cursorStyles`, les événements de déplacement globaux pour le drag, la configuration `eventFeatures`. Se déclenche sur : `eventMode`, `FederatedPointerEvent`, `pointerdown`, `click`, `tap`, `globalpointermove`, drag, `hitArea`, `cursor`, `stopPropagation`.

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

Le système d'événements fédéré de PixiJS reflète les événements DOM sur le graphique de scène. Définissez container.eventMode = 'static' pour activer un objet, puis écoutez avec .on(), addEventListener(), ou les gestionnaires de propriété onEventName. Les événements de mouvement ne se déclenchent que sur l'objet qui écoute ; utilisez globalpointermove pour le drag.

Démarrage rapide

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

Compétences associées : pixijs-accessibility (lecteur d'écran + clavier), pixijs-scene-dom-container (superpositions HTML), pixijs-performance (scènes riches en événements).

Modèles fondamentaux

Valeurs de eventMode

import { Sprite } from "pixi.js";

const sprite = new Sprite();

// Aucune interaction du tout ; les enfants sont aussi ignorés
sprite.eventMode = "none";

// Par défaut. Self non interactif ; les enfants interactifs fonctionnent quand même
sprite.eventMode = "passive";

// Testé au hit uniquement quand un parent est interactif
sprite.eventMode = "auto";

// Interaction standard : reçoit les événements pointer/mouse/touch
sprite.eventMode = "static";

// Comme static, mais déclenche aussi les événements synthétiques du ticker
// quand le pointeur est stationnaire (pour les objets animés sous le curseur)
sprite.eventMode = "dynamic";

Utilisez 'static' pour les boutons, éléments UI et cibles de drag. Utilisez 'dynamic' uniquement pour les objets qui se déplacent sous un curseur stationnaire et qui ont besoin de mises à jour de survol continues.

Utilisez isInteractive() pour vérifier si un objet peut recevoir des événements :

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

sprite.eventMode = "passive";
sprite.isInteractive(); // false

Types d'événements

Événements pointer (recommandés pour la compatibilité inter-appareils) : pointerdown, pointerup, pointerupoutside, pointermove, pointerover, pointerout, pointerenter, pointerleave, pointertap, pointercancel.

Événements souris : mousedown, mouseup, mouseupoutside, mousemove, mouseover, mouseout, mouseenter, mouseleave, click, rightdown, rightup, rightupoutside, rightclick, wheel.

Événements tactiles : touchstart, touchend, touchendoutside, touchmove, touchcancel, tap. Chaque toucher porte altKey, ctrlKey, metaKey et shiftKey copiés du TouchEvent natif, donc les touches de modification fonctionnent de la même manière qu'avec les événements souris ou pointer.

Événements de mouvement global : globalpointermove, globalmousemove, globaltouchmove. Ceux-ci se déclenchent à chaque mouvement du pointeur, que le pointeur soit ou non au-dessus de l'objet qui écoute.

Événements du cycle de vie des conteneurs (pas eventMode requis) : added, removed, destroyed, childAdded, childRemoved, visibleChanged.

Styles d'écoute

import { Sprite } from "pixi.js";

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

// Style EventEmitter (recommandé)
const handler = (e) => console.log("clicked");
sprite.on("pointerdown", handler);
sprite.once("pointerdown", handler); // une seule fois
sprite.off("pointerdown", handler);

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

// Gestionnaires basés sur les propriétés
sprite.onclick = (event) => {
  console.log("Clicked!", event.detail);
};

Événements pointer et propagation

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(); // empêche le parent de recevoir cet événement
});

parent.on("pointerdown", () => {
  console.log("parent pressed (only if child did not stop propagation)");
});

Événements de phase de capture

Tous les événements supportent la phase de capture en ajoutant capture au nom de l'événement (par ex. pointerdowncapture, clickcapture). Les écouteurs de capture se déclenchent pendant la phase de capture, avant que l'événement n'atteigne sa cible.

container.addEventListener(
  "pointerdown",
  (event) => {
    event.stopImmediatePropagation(); // empêche l'événement d'atteindre les enfants
  },
  { capture: true },
);

Test de collision

Quand un événement pointer se déclenche, PixiJS parcourt l'arbre d'affichage pour trouver l'élément interactif le plus haut sous le pointeur. La traversée suit ces règles :

  • eventMode = 'none' sur un conteneur saute cet élément et tout son sous-arbre.
  • interactiveChildren = false sur un conteneur saute ses enfants (le conteneur lui-même peut toujours être testé).
  • Un hitArea remplace le test basé sur les limites ; seule la forme est vérifiée.
  • Les objets qui ne sont pas visibles, ne sont pas rendus ou ne sont pas mesurables sont ignorés.

Définissez un hitArea personnalisé pour remplacer le test basé sur les limites. Cela accélère aussi les tests de collision sur les objets volumineux ou complexes en réduisant la géométrie vérifiée :

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

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

// Zone de collision rectangulaire
sprite.hitArea = new Rectangle(0, 0, 100, 50);

// Zone de collision circulaire
sprite.hitArea = new Circle(50, 50, 40);

// Zone de collision polygonale
sprite.hitArea = new Polygon([0, 0, 100, 0, 50, 100]);

// Test de collision personnalisé via contains()
sprite.hitArea = {
  contains(x: number, y: number): boolean {
    return x >= 0 && x <= 100 && y >= 0 && y <= 100;
  },
};

Événements de mouvement global et drag

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 se déclenche même quand le pointeur quitte l'objet
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";
});

Styles de curseur

L'utilisation de base définit la propriété cursor par objet. Pour les curseurs réutilisables, enregistrez les styles nommés sur le système d'événements :

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"; // utilise le style 'hover' enregistré

Les styles de curseur peuvent être des chaînes (valeurs de curseur CSS), des objets (appliqués comme styles CSS), ou des fonctions (appelées avec la chaîne de mode).

Propriétés d'événement

FederatedPointerEvent porte des données d'entrée riches ; les champs les plus utiles sont :

sprite.on("pointerdown", (event: FederatedPointerEvent) => {
  event.global; // Point en espace scène où l'événement s'est produit
  event.client; // Point en pixels CSS relatif à la fenêtre d'affichage
  event.offset; // Point w.r.t. cible Container en espace monde (pas supporté pour le moment)
  event.target; // le Container qui a reçu l'événement
  event.currentTarget; // le Container dont l'écouteur s'exécute

  event.pointerType; // 'mouse' | 'pen' | 'touch'
  event.pointerId; // ID unique pour le suivi multi-tactile
  event.isPrimary; // premier pointeur dans un geste multi-pointeur
  event.pressure; // pression stylet/tactile 0-1
  event.button; // 0 gauche, 1 milieu, 2 droit
  event.buttons; // masque binaire des boutons maintenus
  event.altKey; // état de la touche de modification
  event.ctrlKey;
  event.shiftKey;
  event.metaKey;

  event.nativeEvent; // le PointerEvent / MouseEvent / Touch DOM sous-jacent
  event.preventDefault();
  event.stopPropagation();
  event.stopImmediatePropagation();
});

FederatedWheelEvent ajoute deltaX, deltaY, deltaZ et deltaMode. Les événements wheel se déclenchent sur le même objet que les événements pointer testés au hit.

Caractéristiques d'événement

Basculez les catégories d'événements globalement pour les performances :

await app.init({
  eventFeatures: {
    move: true, // événements de mouvement pointer/mouse/touch
    globalMove: true, // événements de mouvement global (globalpointermove, etc.)
    click: true, // événements click/tap/press
    wheel: true, // événements de molette souris
  },
});

// ou configurez après init
app.renderer.events.features.globalMove = false;

Conseils de performance

  • Définissez eventMode = 'none' sur les sous-arborescences non interactives pour sauter entièrement le test de collision.
  • Définissez interactiveChildren = false sur les conteneurs où seul le conteneur lui-même a besoin d'interaction.
  • Utilisez hitArea sur les objets volumineux ou complexes pour remplacer le test de collision basé sur les limites par une vérification de forme bon marché.
  • Préférez 'static' pour les éléments stationnaires ; réservez 'dynamic' pour les objets qui se déplacent ou s'animent sous un pointeur stationnaire.
  • Désactivez les caractéristiques d'événement inutilisées via eventFeatures (par ex. globalMove: false) pour réduire le travail par image.

Erreurs courantes

[HIGH] Le eventMode par défaut est passive

Faux :

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

Correct :

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

Le eventMode par défaut est 'passive', ce qui signifie que l'objet lui-même ne reçoit aucun événement. Vous devez définir explicitement eventMode à 'static' ou 'dynamic' avant que tout écouteur se déclenche.

[HIGH] buttonMode supprimé ; utilisez cursor

Faux :

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

Correct :

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

buttonMode a été supprimé dans v8. Utilisez cursor = 'pointer' pour afficher un curseur en main au survol. interactive = true fonctionne toujours comme alias pour eventMode = 'static', mais eventMode est préféré.

[HIGH] Les événements de mouvement ne se déclenchent que sur l'objet en v8

Faux :

sprite.eventMode = "static";
sprite.on("pointermove", (event) => {
  // s'attend à se déclencher partout ; ne se déclenche que dans les limites du sprite
  updateDrag(event.global.x, event.global.y);
});

Correct :

sprite.eventMode = "static";
sprite.on("globalpointermove", (event) => {
  // se déclenche partout, même en dehors des limites du sprite
  updateDrag(event.global.x, event.global.y);
});

En v8, pointermove, mousemove et touchmove ne se déclenchent que quand le pointeur est au-dessus de l'objet d'affichage. En v7 ils se déclenchaient sur n'importe quel mouvement de canevas. Pour les opérations de drag ou le suivi global, utilisez globalpointermove, globalmousemove ou globaltouchmove.

[MEDIUM] Le curseur ne s'hérite pas du parent

Définir cursor sur un conteneur parent n'a aucun effet sur ses enfants. Seule la valeur cursor de la cible directe du hit est appliquée.

// Cela ne rend PAS les enfants affichant un curseur pointeur
parent.cursor = "pointer";

// Chaque enfant interactif a besoin de son propre curseur
child.eventMode = "static";
child.cursor = "pointer";

Si vous voulez un curseur uniforme pour tous les enfants, définissez cursor sur chaque enfant interactif individuellement, ou définissez hitArea sur le parent et rendez les enfants non-interactifs.

Référence API

Skills similaires