Three.js Double Click Raycaster Intersection Tutorial
This article explains how to detect double-click events on 3D objects within a Three.js scene using a Raycaster. You will learn how to capture the mouse coordinates during a double-click event, convert those coordinates into normalized device coordinates, and cast a ray from the camera to identify which 3D objects in your scene were clicked.
Step-by-Step Implementation
To trigger a Raycaster intersection on a double-click, you need to
set up a standard DOM event listener for the dblclick
event, convert the mouse coordinates, and update the Three.js
Raycaster.
1. Initialize the Raycaster and Pointer Vectors
First, instantiate a global Raycaster and a
Vector2 to store the normalized mouse coordinates.
import * as THREE from 'three';
const raycaster = new THREE.Raycaster();
const pointer = new THREE.Vector2();2. Create the Double-Click Event Listener
Add an event listener for dblclick to your window or the
specific canvas rendering your Three.js scene.
Inside the listener, calculate the mouse’s normalized device coordinates (NDC). This maps the 2D screen coordinates of the double-click (spanning from top-left to bottom-right) to a 3D coordinate system ranging from -1 to 1 on both axes.
window.addEventListener('dblclick', onDoubleMouseClick);
function onDoubleMouseClick(event) {
// Calculate pointer position in normalized device coordinates (-1 to +1)
pointer.x = (event.clientX / window.innerWidth) * 2 - 1;
pointer.y = -(event.clientY / window.innerHeight) * 2 + 1;
// Trigger the raycast calculation
checkIntersections();
}3. Cast the Ray and Detect Intersections
With the pointer coordinates updated, configure the raycaster to
point from the camera through the mouse coordinates. Then, use
intersectObjects to find which meshes in your scene the ray
passes through.
function checkIntersections() {
// Update the picking ray with the camera and pointer position
raycaster.setFromCamera(pointer, camera);
// Calculate objects intersecting the picking ray
// Set the second parameter to true to check all descendants (recursive)
const intersects = raycaster.intersectObjects(scene.children, true);
if (intersects.length > 0) {
// The first object in the array is the closest intersected object
const targetObject = intersects[0].object;
console.log('Double-clicked on:', targetObject);
// Example action: Change the color of the double-clicked object
if (targetObject.material && targetObject.material.color) {
targetObject.material.color.set(0xff0000); // Turn red
}
}
}Complete Code Example
Here is how the code fits together inside a typical Three.js setup:
import * as THREE from 'three';
// Standard Scene, Camera, and Renderer Setup
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// Add a test cube to the scene
const geometry = new THREE.BoxGeometry();
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);
camera.position.z = 5;
// Raycasting Variables
const raycaster = new THREE.Raycaster();
const pointer = new THREE.Vector2();
// Event Listener
window.addEventListener('dblclick', (event) => {
// 1. Convert screen coordinates to normalized device coordinates
pointer.x = (event.clientX / window.innerWidth) * 2 - 1;
pointer.y = -(event.clientY / window.innerHeight) * 2 + 1;
// 2. Set the raycaster
raycaster.setFromCamera(pointer, camera);
// 3. Test for intersections
const intersects = raycaster.intersectObjects(scene.children);
if (intersects.length > 0) {
// Handle double-click action
intersects[0].object.material.color.set(0xff00ff); // Change color to magenta
}
});
// Animation Loop
function animate() {
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
animate();