Pixi.js Multi-Touch Events on Mobile Devices

Modern mobile web applications rely heavily on responsive touch interactions, and Pixi.js provides a robust system to manage them. This article explores how Pixi.js handles multi-touch events on mobile devices, covering its internal EventSystem, pointer event abstraction, and practical implementation strategies for tracking multiple simultaneous touches.

The Foundation: Unified Pointer Events

Pixi.js simplifies input handling by using the W3C Pointer Events API. Instead of forcing developers to write separate codebases for mouse, touch, and pen inputs, Pixi.js abstracts these into unified “pointer” events.

On mobile devices, when multiple fingers touch the screen, the browser fires standard touch events. Pixi.js intercepts these events and maps each individual touch point to a corresponding Pixi pointer event. The primary events used for multi-touch interaction include:

Enabling Interactivity in Pixi.js

To make a display object (like a Sprite or Container) responsive to touch, you must explicitly enable its interactivity. In modern versions of Pixi.js (v7 and v8), this is achieved using the eventMode property.

const sprite = new PIXI.Sprite(texture);
sprite.eventMode = 'static'; // Makes the object interactive

By setting eventMode to 'static' (or 'dynamic' for moving objects), Pixi.js includes the object in its hit-testing passes.

Tracking Multiple Touches with Pointer IDs

The key to handling multi-touch in Pixi.js is the pointerId property. Every active touch on a mobile screen is assigned a unique identifier by the browser, which Pixi.js exposes via the event object.

To track multiple fingers independently, you must store the coordinates of each touch mapped to its respective pointerId.

Practical Implementation

Here is how to track and respond to multi-touch events on a single interactive object:

const activeTouches = new Map();

sprite.on('pointerdown', (event) => {
    // Store the initial touch position using the unique pointerId
    activeTouches.set(event.pointerId, {
        x: event.global.x,
        y: event.global.y
    });
});

sprite.on('pointermove', (event) => {
    // Check if this moving pointer is one we are tracking
    if (activeTouches.has(event.pointerId)) {
        const touch = activeTouches.get(event.pointerId);
        
        // Update to the new position
        touch.x = event.global.x;
        touch.y = event.global.y;
        
        // Handle custom multi-touch logic here (e.g., pinch-to-zoom, rotation)
        handleMultiTouchLogic();
    }
});

const endTouch = (event) => {
    // Remove the touch pointer when the finger is lifted
    activeTouches.delete(event.pointerId);
};

sprite.on('pointerup', endTouch);
sprite.on('pointerupoutside', endTouch);

Implementing Complex Gestures (Pinch and Zoom)

When multiple touches are registered inside activeTouches, you can calculate the distance and angle between the touch points to implement complex mobile gestures:

  1. Pinch-to-Zoom: Calculate the distance between two active pointer coordinates on pointerdown. On pointermove, recalculate the distance. The ratio between the new distance and the starting distance determines the scale factor.
  2. Rotation: Calculate the angle between two pointers using Math.atan2. Track the change in angle during pointermove to rotate display objects.

Mobile Best Practices

For optimal multi-touch performance in Pixi.js on mobile browsers, consider these optimizations: