Threejs Interaction

Add rich user interactions to Three.js scenes with automation and integration

Three.js Interaction is a community skill for adding user interaction to Three.js scenes, covering raycasting, mouse and touch events, drag controls, object selection, and responsive interaction for 3D web experiences.

What Is This?

Overview

Three.js Interaction provides guidance on implementing user interaction with 3D objects in Three.js scenes. It covers raycasting that detects which objects the user clicks or hovers over in 3D space, mouse and touch events that translate screen coordinates to 3D world positions, drag controls that enable object manipulation through click-and-drag gestures, object selection that highlights and tracks user-picked objects with visual feedback, and responsive interaction that handles input across desktop and mobile devices. The skill helps developers build interactive 3D applications.

Who Should Use This

This skill serves web developers building interactive 3D product viewers, game developers adding click and drag mechanics, and creative developers building immersive web experiences.

Why Use It?

Problems It Solves

3D scenes without interaction feel static and unengaging. Converting 2D screen clicks to 3D object hits requires raycasting knowledge. Implementing drag-and-drop in 3D space needs proper coordinate transformation. Touch and mouse events behave differently and need unified handling.

Core Highlights

Raycaster detects 3D object intersections from screen clicks. Event handler normalizes mouse and touch input. Drag controller enables object movement in 3D space. Selection manager tracks picked objects with visual highlights.

How to Use It?

Basic Usage

// Raycasting interaction
import * as THREE from
  'three';

class Interactor {
  constructor(
    camera, domElement
  ) {
    this.camera = camera;
    this.raycaster =
      new THREE.Raycaster();
    this.mouse =
      new THREE.Vector2();
    this.objects = [];
    this.selected = null;

    domElement
      .addEventListener(
        'click',
        (e) => this
          .onClick(e));
    domElement
      .addEventListener(
        'mousemove',
        (e) => this
          .onMove(e));
  }

  onClick(event) {
    this._updateMouse(
      event);
    this.raycaster
      .setFromCamera(
        this.mouse,
        this.camera);
    const hits =
      this.raycaster
      .intersectObjects(
        this.objects);
    if (hits.length > 0) {
      this.select(
        hits[0].object);
    } else {
      this.deselect();
    }
  }

  onMove(event) {
    this._updateMouse(
      event);
  }

  select(obj) {
    this.deselect();
    this.selected = obj;
    obj.material
      .emissive?.set(
        0x444444);
  }

  deselect() {
    if (this.selected) {
      this.selected
        .material
        .emissive?.set(0);
      this.selected = null;
    }
  }

  _updateMouse(e) {
    this.mouse.x =
      (e.clientX
        / innerWidth)
      * 2 - 1;
    this.mouse.y =
      -(e.clientY
        / innerHeight)
      * 2 + 1;
  }
}

// const inter =
//   new Interactor(
//     camera, canvas);
// inter.objects = cubes;

Real-World Examples

// Drag controls
import * as THREE from
  'three';

class DragHandler {
  constructor(
    camera, plane
  ) {
    this.camera = camera;
    this.plane = plane;
    this.raycaster =
      new THREE.Raycaster();
    this.mouse =
      new THREE.Vector2();
    this.dragging = null;
    this.offset =
      new THREE.Vector3();
    this.intersection =
      new THREE.Vector3();
  }

  startDrag(
    object, event
  ) {
    this.dragging = object;
    this._getPlanePoint(
      event);
    this.offset.copy(
      this.intersection)
      .sub(
        object.position);
  }

  drag(event) {
    if (!this.dragging)
      return;
    this._getPlanePoint(
      event);
    this.dragging
      .position.copy(
        this.intersection)
      .sub(this.offset);
  }

  endDrag() {
    this.dragging = null;
  }

  _getPlanePoint(e) {
    this.mouse.set(
      (e.clientX
        / innerWidth)
      * 2 - 1,
      -(e.clientY
        / innerHeight)
      * 2 + 1);
    this.raycaster
      .setFromCamera(
        this.mouse,
        this.camera);
    this.raycaster.ray
      .intersectPlane(
        this.plane,
        this.intersection);
  }
}

const groundPlane =
  new THREE.Plane(
    new THREE.Vector3(
      0, 1, 0), 0);
// const dragger =
//   new DragHandler(
//     camera,
//     groundPlane);

Advanced Tips

Set raycaster layers to limit intersection tests to interactive objects for better performance. Use pointer events instead of separate mouse and touch handlers for unified input. Throttle mousemove raycasts to reduce CPU overhead during continuous hovering.

When to Use It?

Use Cases

Build a product configurator where users click to select parts and change colors. Create a 3D editor with drag-and-drop object placement on a ground plane. Implement hover tooltips that display information when the cursor passes over 3D objects.

Related Topics

Three.js, raycasting, mouse events, touch interaction, drag controls, 3D picking, and user input handling.

Important Notes

Requirements

Three.js library with Raycaster support for intersection testing. A canvas element with proper event listeners for mouse and touch input. Objects using materials with appropriate properties for visual selection feedback.

Usage Recommendations

Do: normalize screen coordinates to the range of negative one to positive one for correct raycasting. Use object layers to separate interactive and non-interactive elements. Provide visual feedback like color changes on hover and selection.

Don't: run raycasts against all scene objects since this is slow for complex scenes. Forget to handle touch events separately since they have different coordinate properties. Attach event listeners to individual 3D objects since Three.js uses raycasting rather than DOM events.

Limitations

Raycasting performance decreases with the number of objects tested per frame. Complex mesh geometry produces slower intersection tests than simple bounding box checks. Touch interaction on mobile lacks hover state which requires alternative UI patterns for discovery.