Extend Three.js EventDispatcher for Custom Events

This article demonstrates how to extend the Three.js EventDispatcher class to implement custom event emission and handling within your own 3D applications. You will learn how to create a custom class that inherits from EventDispatcher, dispatch custom events with payload data, and register listeners to respond to those events dynamically.

Understanding EventDispatcher

In Three.js, the EventDispatcher class provides a lightweight implementation of the observer pattern. It allows objects to listen for, receive, and dispatch events. While core Three.js classes like Object3D inherit from EventDispatcher by default, you can also extend it directly to add event-driven architecture to your own custom logic, managers, or interactive components.

Step-by-Step Implementation

To implement custom event emission, you need to import the EventDispatcher class, extend it using ES6 class syntax, and use the dispatchEvent method to broadcast events.

1. Create the Custom Class

Create a class that extends EventDispatcher. Call super() in the constructor to properly initialize the parent class.

import { EventDispatcher } from 'three';

class InteractiveControl extends EventDispatcher {
  constructor(name) {
    super();
    this.name = name;
  }

  // A custom method that triggers an event
  activate() {
    console.log(`${this.name} activated.`);
    
    // Dispatching the custom event
    this.dispatchEvent({
      type: 'activate',
      message: `${this.name} has been successfully activated.`,
      timestamp: Date.now()
    });
  }
}

2. Listen to and Handle the Custom Event

Once your class is defined, you can instantiate it and use addEventListener to listen for the custom event type you specified in dispatchEvent.

// Instantiate the custom object
const control = new InteractiveControl('Main Button');

// Define the event listener callback
function onActivate(event) {
  // The event object contains 'type' and any custom payload properties
  console.log(`Event Received: ${event.type}`);
  console.log(`Payload Message: ${event.message}`);
  console.log(`Time: ${event.timestamp}`);
}

// Register the listener
control.addEventListener('activate', onActivate);

// Trigger the method that dispatches the event
control.activate();

3. Removing Event Listeners

To prevent memory leaks, especially in complex applications, ensure you remove listeners when they are no longer needed.

// Remove the specific event listener
control.removeEventListener('activate', onActivate);

Checking for Existing Listeners

You can also check if a specific object has any active listeners for a particular event type using the hasEventListener method.

if (control.hasEventListener('activate', onActivate)) {
  console.log('Listener is active.');
}

Adding EventDispatcher to Existing Classes (Alternative)

If your class must extend another base class (such as THREE.Mesh), you cannot use multiple inheritance directly in JavaScript. Instead, you can copy the prototype methods of EventDispatcher onto your class using Object.assign.

import { Mesh, EventDispatcher } from 'three';

class CustomMesh extends Mesh {
  constructor(geometry, material) {
    super(geometry, material);
  }
}

// Mixin EventDispatcher prototype into CustomMesh
Object.assign(CustomMesh.prototype, EventDispatcher.prototype);

// Usage remains the same
const myMesh = new CustomMesh(geometry, material);
myMesh.addEventListener('select', (e) => console.log('Mesh selected'));
myMesh.dispatchEvent({ type: 'select' });