Three.js Raycaster: Click and Hover Detection
THREE.Raycaster finds which 3D objects a ray intersects. Cast a ray from the camera through the mouse's normalized device coordinates with raycaster.setFromCamera(mouse, camera), then call raycaster.intersectObjects(objects) to get a sorted list of hits, which is how Three.js implements clicking, hovering and dragging 3D objects with a 2D mouse.
The standard click/hover pattern
Raycasting for mouse interaction always follows the same three steps:
- Convert mouse pixel coordinates to normalized device coordinates (NDC). This means a range of -1 to +1 on both axes, with (0,0) at the screen center. It's not the same as the raw
clientX/clientYpixel values; forgetting to normalize is the most common raycaster bug. - Update the raycaster with
raycaster.setFromCamera(mouseNDC, camera), which computes the ray from the camera through that screen point. - Intersect with
raycaster.intersectObjects(objects, recursive). The result is an array sorted by distance, nearest first, sointersects[0]is what the user actually clicked or hovered, even if other objects are behind it along the same ray.
Each intersection result includes object, point (the world-space hit position), distance, face and uv, enough to place a decal, read which triangle was hit, or sample a texture at the click point.
Hover highlighting vs click selection
Hover detection runs the same raycast on pointermove, typically every frame or throttled to the mouse-move event, and swaps the previously-hovered object's material or emissive color back before highlighting the new one. Tracking the "currently hovered" object explicitly avoids flicker when the ray briefly hits nothing between two overlapping objects.
Click selection is the same raycast run once on pointerdown/click. A common mistake: forgetting to check intersects.length > 0 before reading intersects[0], which throws on an empty click.
Keeping raycasting fast in large scenes
Raycasting against every mesh in a large scene every frame gets expensive. The standard mitigations:
- Throttle hover raycasts. You rarely need to raycast on every single
pointermoveevent; every other frame or a small debounce is usually imperceptible. - Restrict the candidate list. Pass only the objects that can actually be interacted with to
intersectObjects(), not the whole scene graph. Group interactive objects under one parent and raycast against its children. - Use bounding volumes for the first pass. For very large scenes, a spatial index (octree, or even a simple bounding-sphere pre-check) can cull most objects before the expensive triangle-level raycast runs.
- Set
recursivecorrectly.intersectObjects(objects, false)skips descending into children when you don't need to, which is cheaper when your interactive objects are flat, non-nested meshes.
Code
Learn this properly
Learn Practical TSL
Your First Node Material
Build the material you'll be selecting with a raycaster in this first lesson.
Frequently asked questions
Why does my Three.js raycaster not detect clicks correctly?
The most common cause is using raw pixel coordinates instead of normalized device coordinates (-1 to +1) when calling setFromCamera. Also check you're passing the right object list to intersectObjects and reading intersects[0], not the whole array, for the nearest hit.
How do I raycast against only certain objects in Three.js?
Pass an explicit array to raycaster.intersectObjects() rather than the whole scene. Group interactive objects under one parent object and raycast against interactiveGroup.children.
Is raycasting expensive in Three.js?
Against a small number of objects, no. It's fast enough to run every frame for hover effects. Against thousands of high-poly meshes, it can become a bottleneck; throttle the raycast frequency and restrict the candidate object list to mitigate it.
Keep reading
OrbitControls
OrbitControls lets a user orbit, zoom and pan a Three.js camera around a target point using mouse or touch. Instantiate …
GLTFLoader
GLTFLoader is the standard way to load 3D models into Three.js: instantiate it, call loader.load(url, onLoad), and add g…
WebGPU vs WebGL
WebGPU is the successor to WebGL: a lower-level, more modern graphics API that exposes compute shaders and reduces CPU o…
All Three.js Guides
Back to the guides hub.