Raycasting in React Three Fiber (R3F) allows you to detect interactions between a virtual "ray" (often originating from the camera and pointing towards the mouse cursor) and objects in your 3D scene. This is fundamental for implementing interactive experiences like clicking on objects, hovering, or dragging.
For typical interactions such as onClick, onPointerOver, onPointerOut, onPointerMove, etc., React Three Fiber automatically handles the underlying raycasting. You can attach these event handlers directly to your mesh components, similar to how you would with DOM elements.
The <Canvas /> component in R3F sets up a raycaster by default. This raycaster's origin and direction are automatically updated based on mouse movement and window resizing, and it uses the currently active camera to determine the ray's path.
import { Canvas } from '@react-three/fiber';
import { Box } from '@react-three/drei'; // A helpful component from @react-three/drei
function MyInteractiveScene() {
return (
<Canvas>
<ambientLight />
<pointLight position={[10, 10, 10]} />
{/* A clickable box */}
<Box
args={[1, 1, 1]} // Dimensions: width, height, depth
position={[0, 0, 0]}
onClick={(event) => {
console.log('Box clicked!', event.object.name);
// You can access properties of the intersected object via event.object
// event.stopPropagation() can be used to prevent events from bubbling up to parent objects
}}
onPointerOver={(event) => {
console.log('Pointer over box');
document.body.style.cursor = 'pointer';
}}
onPointerOut={(event) => {
console.log('Pointer out of box');
document.body.style.cursor = 'auto';
}}
>
<meshStandardMaterial color="hotpink" />
</Box>
{/* Another clickable sphere */}
<mesh
position={[2, 0, 0]}
onClick={() => console.log('Sphere clicked!')}
>
<sphereGeometry args={[0.7, 32, 32]} />
<meshStandardMaterial color="royalblue" />
</mesh>
</Canvas>
);
}
export default MyInteractiveScene;
While R3F's built-in events are powerful, there might be situations where you need more control, such as:
In these cases, you can directly use Three.js's Raycaster class within your R3F components. You can access the WebGLRenderer (gl), Camera (camera), and Scene (scene) objects from the useThree hook provided by R3F.
import { Canvas, useThree } from '@react-three/fiber';
import { useEffect } from 'react';
import *s THREE from 'three';
function RaycasterHandler() {
const { gl, camera, scene } = useThree();
useEffect(() => {
const raycaster = new THREE.Raycaster();
const pointer = new THREE.Vector2();
const onCanvasClick = (event) => {
// Calculate pointer position in normalized device coordinates (-1 to +1)
pointer.x = (event.clientX / gl.domElement.clientWidth) * 2 - 1;
pointer.y = -(event.clientY / gl.domElement.clientHeight) * 2 + 1;
// Update the raycaster with the camera and pointer position
raycaster.setFromCamera(pointer, camera);
// Calculate objects intersecting the ray
// The second argument (true) makes it recursive, checking children of objects
const intersects = raycaster.intersectObjects(scene.children, true);
if (intersects.length > 0) {
console.log('Manual Raycast: Intersected object(s):', intersects);
} else {
console.log('Manual Raycast: Clicked on empty space.');
}
};
// Attach the event listener to the canvas DOM element
gl.domElement.addEventListener('click', onCanvasClick);
// Clean up the event listener
return () => {
gl.domElement.removeEventListener('click', onCanvasClick);
};
}, [gl, camera, scene]);
return null;
}
function MySceneWithManualRaycasting() {
return (
<Canvas>
<ambientLight />
<pointLight position={[10, 10, 10]} />
<mesh position={[0, 0, 0]}>
<boxGeometry args={[1, 1, 1]} />
<meshStandardMaterial color="orange" />
</mesh>
<RaycasterHandler />
</Canvas>
);
}