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 = falsesur un conteneur saute ses enfants (le conteneur lui-même peut toujours être testé).- Un
hitArearemplace 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 = falsesur les conteneurs où seul le conteneur lui-même a besoin d'interaction. - Utilisez
hitAreasur 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.