In this blog post we’ll cover the code required to set up a clickable 3D object using three.js that behave like the objects on our clickable three.js object demo page. Let’s get into 3D objects with click handlers.
Looking to get a head start on your next software interview? Pickup a copy of the best book to prepare: Cracking The Coding Interview!
The three.js javascript library is a powerful library that uses WebGL to render 3D animations in modern browsers. Once we have included the three.js script <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r73/three.min.js"></script> we can instantiate a 3D object using the following code:
// three.js variables
var mesh, mesh2, mesh3, camera, scene, renderer;
var maxRotation = 2 * Math.PI;
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function init() {
camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.z = 400;
scene = new THREE.Scene();
var texture = new THREE.TextureLoader().load('https://pericror.com/wp-content/uploads/2018/04/pericrorLogoBox.png');
var material = new THREE.MeshBasicMaterial({
map: texture
});
var objectSize = 100;
// Create a cube
var boxGeometry = new THREE.BoxGeometry(objectSize, objectSize, objectSize);
mesh = new THREE.Mesh(boxGeometry, material);
mesh.position.set(objectSize * -2, 0, 0);
mesh.callback = objectClickHandler;
scene.add(mesh);
renderer = new THREE.WebGLRenderer({
alpha: true
});
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
var container = document.getElementById('canvasContainer');
container.appendChild(renderer.domElement);
window.addEventListener('resize', onWindowResize, false);
}
window.onload = function() {
init();
};
To add additional shapes, we use different mesh geometries:
// Create a cube var boxGeometry = new THREE.BoxGeometry(objectSize, objectSize, objectSize); mesh = new THREE.Mesh(boxGeometry, material); mesh.position.set(objectSize * -2, 0, 0); mesh.callback = objectClickHandler; // create a sphere var sphereGeometry = new THREE.SphereGeometry( objectSize / 2, 32, 32 ); mesh2 = new THREE.Mesh(sphereGeometry, material); mesh2.position.set(0, 0, 0); mesh2.callback = objectClickHandler; // create a cylinder var cylinderGeometry = new THREE.CylinderGeometry( objectSize / 4, objectSize / 4, 20, 32 ); mesh3 = new THREE.Mesh(cylinderGeometry, material); mesh3.position.set(objectSize * 2, 0, 0); mesh3.callback = objectClickHandler; scene.add(mesh); scene.add(mesh2); scene.add(mesh3);
To make the objects move, we animate them by rotating them on the y axis:
function animate() {
requestAnimationFrame(animate);
mesh.rotation.y = (mesh.rotation.y + 0.005) % maxRotation;
mesh2.rotation.y = (mesh2.rotation.y + 0.005) % maxRotation;
mesh3.rotation.y = (mesh3.rotation.y + 0.005) % maxRotation;
renderer.render(scene, camera);
}
window.onload = function() {
init();
animate();
Now that we have the shapes created and animated, we need to make them clickable. A solution from StackOverflow shows us how we can use a raycaster to test if our mouse intersects a 3D object:
// Default click handler for our three.js objects
function objectClickHandler() {
window.open('https://pericror.com/', '_blank');
}
window.onload = function() {
init();
animate();
var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2();
// See https://stackoverflow.com/questions/12800150/catch-the-click-event-on-a-specific-mesh-in-the-renderer
// Handle all clicks to determine of a three.js object was clicked and trigger its callback
function onDocumentMouseDown(event) {
event.preventDefault();
mouse.x = (event.clientX / renderer.domElement.clientWidth) * 2 - 1;
mouse.y = - (event.clientY / renderer.domElement.clientHeight) * 2 + 1;
raycaster.setFromCamera(mouse, camera);
meshObjects = [mesh, mesh2, mesh3]; // three.js objects with click handlers we are interested in
var intersects = raycaster.intersectObjects(meshObjects);
if (intersects.length > 0) {
intersects[0].object.callback();
}
}
document.addEventListener('mousedown', onDocumentMouseDown, false);
};
We can also use the same intersection logic to optionally add feedback in the object motion when a user mouses over the object:
// Using the same logic as above, determine if we are currently mousing over a three.js object,
// and adjust the animation to provide visual feedback accordingly
function onDocumentMouseMove(event) {
event.preventDefault();
mouse.x = (event.clientX / renderer.domElement.clientWidth) * 2 - 1;
mouse.y = - (event.clientY / renderer.domElement.clientHeight) * 2 + 1;
raycaster.setFromCamera(mouse, camera);
var intersects = raycaster.intersectObjects([mesh, mesh2, mesh3]);
var canvas = document.body.getElementsByTagName('canvas')[0];
if (intersects.length > 0) {
intersects[0].object.rotation.x += .005;
canvas.style.cursor = "pointer";
} else {
canvas.style.cursor = "default";
}
}
document.addEventListener('mousedown', onDocumentMouseDown, false);
document.addEventListener('mousemove', onDocumentMouseMove, false);
The full code to produce the objects above:
// three.js variables
var mesh, mesh2, mesh3, camera, scene, renderer;
var maxRotation = 2 * Math.PI;
// Default click handler for our three.js objects
function objectClickHandler() {
window.open('https://pericror.com/', '_blank');
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function init() {
camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.z = 400;
scene = new THREE.Scene();
var texture = new THREE.TextureLoader().load('https://pericror.com/wp-content/uploads/2018/04/pericrorLogoBox.png');
var material = new THREE.MeshBasicMaterial({
map: texture
});
var objectSize = 100;
// Create a cube
var boxGeometry = new THREE.BoxGeometry(objectSize, objectSize, objectSize);
mesh = new THREE.Mesh(boxGeometry, material);
mesh.position.set(objectSize * -2, 0, 0);
mesh.callback = objectClickHandler;
// create a sphere
var sphereGeometry = new THREE.SphereGeometry( objectSize / 2, 32, 32 );
mesh2 = new THREE.Mesh(sphereGeometry, material);
mesh2.position.set(0, 0, 0);
mesh2.callback = objectClickHandler;
// create a cylinder
var cylinderGeometry = new THREE.CylinderGeometry( objectSize / 4, objectSize / 4, 20, 32 );
mesh3 = new THREE.Mesh(cylinderGeometry, material);
mesh3.position.set(objectSize * 2, 0, 0);
mesh3.callback = objectClickHandler;
scene.add(mesh);
scene.add(mesh2);
scene.add(mesh3);
renderer = new THREE.WebGLRenderer({
alpha: true
});
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
var container = document.getElementById('canvasContainer');
container.appendChild(renderer.domElement);
window.addEventListener('resize', onWindowResize, false);
}
function animate() {
requestAnimationFrame(animate);
mesh.rotation.y = (mesh.rotation.y + 0.005) % maxRotation;
mesh2.rotation.y = (mesh2.rotation.y + 0.005) % maxRotation;
mesh3.rotation.y = (mesh3.rotation.y + 0.005) % maxRotation;
renderer.render(scene, camera);
}
window.onload = function() {
init();
animate();
var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2();
// See https://stackoverflow.com/questions/12800150/catch-the-click-event-on-a-specific-mesh-in-the-renderer
// Handle all clicks to determine of a three.js object was clicked and trigger its callback
function onDocumentMouseDown(event) {
event.preventDefault();
mouse.x = (event.clientX / renderer.domElement.clientWidth) * 2 - 1;
mouse.y = - (event.clientY / renderer.domElement.clientHeight) * 2 + 1;
raycaster.setFromCamera(mouse, camera);
meshObjects = [mesh, mesh2, mesh3]; // three.js objects with click handlers we are interested in
var intersects = raycaster.intersectObjects(meshObjects);
if (intersects.length > 0) {
intersects[0].object.callback();
}
}
// Using the same logic as above, determine if we are currently mousing over a three.js object,
// and adjust the animation to provide visual feedback accordingly
function onDocumentMouseMove(event) {
event.preventDefault();
mouse.x = (event.clientX / renderer.domElement.clientWidth) * 2 - 1;
mouse.y = - (event.clientY / renderer.domElement.clientHeight) * 2 + 1;
raycaster.setFromCamera(mouse, camera);
var intersects = raycaster.intersectObjects([mesh, mesh2, mesh3]);
var canvas = document.body.getElementsByTagName('canvas')[0];
if (intersects.length > 0) {
intersects[0].object.rotation.x += .005;
canvas.style.cursor = "pointer";
} else {
canvas.style.cursor = "default";
}
}
document.addEventListener('mousedown', onDocumentMouseDown, false);
document.addEventListener('mousemove', onDocumentMouseMove, false);
};
The full code for this blog post can be found on our Github. This is a great way to quickly create 3D objects with click handlers.
Elevate your software skills
Ergonomic Mouse![]() |
Custom Keyboard![]() |
SW Architecture![]() |
Clean Code![]() |






if (intersects.length &gt; 0) {
intersects[0].object.callback();
}
how this command works and why exactly are you using this?
please explain.
Thank You.
Imagine there is a camera looking at the 3D scene positioned where the mouse is. We shoot a ray into the scene and if it intersects with a object we fire the callback for the object.
This is a good example.
How can i have each object linked to different urls?
Hi, you can change the mesh callback to a new click handler function that is defined with the other link (i.e. mesh2.callback = objectClickHandler2;)
Good implementation of event handlers of 3d objects.