Track which objects users click in three.js
To track clicks on objects in three.js, you need to do two things: turn each click into a raycast that finds the object under the pointer, and store the object’s name somewhere you can count it. You can build this yourself in about ten lines, then decide whether you also want to build everything around it.
Build it with a Raycaster
Section titled “Build it with a Raycaster”Convert the click to normalized device coordinates, cast a ray from the camera, and report the nearest hit:
import * as THREE from "three";
const raycaster = new THREE.Raycaster();const pointer = new THREE.Vector2();
renderer.domElement.addEventListener("click", (event) => { const rect = renderer.domElement.getBoundingClientRect(); pointer.x = ((event.clientX - rect.left) / rect.width) * 2 - 1; pointer.y = -((event.clientY - rect.top) / rect.height) * 2 + 1;
raycaster.setFromCamera(pointer, camera); const [hit] = raycaster.intersectObjects(scene.children, true); if (!hit) return;
sendToYourAnalytics({ object: hit.object.name, point: hit.point.toArray() });});This is enough to log a click. A usable report also needs:
- Delivery: batching events, and flushing them when the visitor closes the tab.
- Storage and ranking: somewhere to keep the events and a way to rank objects across sessions.
- Misses: clicks that hit nothing are often the most useful ones, and the snippet above drops them.
- Pointer lock: in first-person scenes the cursor is hidden, so the aim point is the centre of
the screen and not
event.clientX.
Use a connector
Section titled “Use a connector”@uptimizr/three does the raycasting and delivery for you and adds
camera, pointer and performance capture:
npm install @uptimizr/three threeimport { trackScene } from "@uptimizr/three";
const client = trackScene(scene, camera, renderer, { projectId: "your-project", endpoint: "https://collect.example.com",});Every click or tap that hits geometry now emits a mesh_interaction event with the mesh name and the
world-space hit point. No further setup is needed. The endpoint is a
collector you run yourself.
Name your meshes
Section titled “Name your meshes”Results are only as good as your object names. The hit is reported using the name of the mesh the ray struck, not its parent group, so name the meshes themselves:
gltf.scene.getObjectByName("Cube012").name = "product-hero";Procedural or clone-suffixed names such as Box.001 and Box.002 split one object across several
rows in your ranking, and an unnamed mesh reports an empty name. Give every interactive mesh a
deliberate, stable name.
See the most-clicked objects
Section titled “See the most-clicked objects”The dashboard shows the ranking as the most-interacted meshes view, and the query API returns the same data:
GET /api/v1/meshes/top?since=...&limit=20x-api-key: your-project-api-keyGo beyond clicks
Section titled “Go beyond clicks”Clicks only show what people successfully acted on. These signals show what they tried and missed:
| You want to know | Use |
|---|---|
| What people click that does nothing | GET /api/v1/clicks/dead reports clicks that hit no geometry |
| Where people get frustrated | GET /api/v1/clicks/rage reports rapid repeated clicks on one mesh |
| What looks interactive but gets no click | hoverDwell |
| What’s on screen but ignored | Blind spots, using meshVisibility |
Hover and visibility capture are off by default. Turn them on when you need them:
const client = trackScene(scene, camera, renderer, { projectId: "your-project", endpoint: "https://collect.example.com", capture: { hoverDwell: true, meshVisibility: true },});Next steps
Section titled “Next steps”- Follow the Quickstart to run a collector and see your first heatmap.
- Read Track 3D object interactions for every capture option.
- Try the live demo to explore the reports without installing anything.