Enable Interactions
Configure interaction features when initializing Threebox:window.tb = new Threebox(
map,
mbxContext,
{
defaultLights: true,
enableSelectingObjects: true, // Click to select 3D objects
enableSelectingFeatures: true, // Click to select buildings
enableDraggingObjects: true, // Drag objects with [Shift]
enableRotatingObjects: true, // Rotate objects with [Alt]
enableTooltips: true, // Show tooltips on hover
enableHelpTooltips: true // Show help for interactions
}
);
Interaction Options
| Option | Type | Default | Description |
|---|---|---|---|
enableSelectingObjects | boolean | false | Enable 3D object selection |
enableSelectingFeatures | boolean | false | Enable fill-extrusion selection |
enableDraggingObjects | boolean | false | Enable object dragging |
enableRotatingObjects | boolean | false | Enable object rotation |
enableTooltips | boolean | false | Show default tooltips |
enableHelpTooltips | boolean | false | Show interaction help labels |
Object Selection
Click on 3D objects to select them:map.on('style.load', function() {
map.addLayer({
id: 'custom_layer',
type: 'custom',
renderingMode: '3d',
onAdd: function(map, mbxContext) {
window.tb = new Threebox(
map,
mbxContext,
{
defaultLights: true,
enableSelectingObjects: true,
enableTooltips: true
}
);
// Create selectable sphere
var sphere = tb.sphere({
radius: 30,
color: 'green',
material: 'MeshPhysicalMaterial',
units: 'meters'
}).setCoords([-122.3512, 47.6202, 0]);
// Add tooltip
sphere.addTooltip("Click to select", true);
// Listen for selection changes
sphere.addEventListener('SelectedChange', function(e) {
if (e.detail.selected) {
console.log('Object selected');
sphere.setColor('yellow');
} else {
console.log('Object unselected');
sphere.setColor('green');
}
}, false);
tb.add(sphere);
},
render: function(gl, matrix) {
tb.update();
}
});
});
Dragging Objects
Drag objects to new positions using keyboard modifiers:- [Shift] + Drag - Translate horizontally
- [Ctrl] + Drag - Adjust altitude
window.tb = new Threebox(
map,
mbxContext,
{
defaultLights: true,
enableSelectingObjects: true,
enableDraggingObjects: true, // Enable dragging
enableTooltips: true
}
);
// Create draggable object
var options = {
obj: 'models/soldier.glb',
type: 'gltf',
scale: 100,
units: 'meters',
rotation: { x: 90, y: 0, z: 0 },
anchor: 'center'
};
tb.loadObj(options, function(model) {
var soldier = model.setCoords([-122.3491, 47.6207, 0]);
soldier.addTooltip("Shift+Drag to move, Ctrl+Drag for altitude", true);
// Listen to drag events
soldier.addEventListener('ObjectDragged', function(e) {
console.log('Dragged action:', e.detail.draggedAction);
console.log('New position:', e.detail.draggedObject.coordinates);
}, false);
tb.add(soldier);
});
Altitude Step
Control altitude adjustment sensitivity:window.tb = new Threebox(
map,
mbxContext,
{
enableDraggingObjects: true
}
);
// Set altitude step (default is 5 meters)
tb.altitudeStep = 1; // 1 meter increments
Rotating Objects
Rotate objects around their vertical axis:- [Alt] + Drag - Rotate on vertical axis
window.tb = new Threebox(
map,
mbxContext,
{
defaultLights: true,
enableSelectingObjects: true,
enableRotatingObjects: true, // Enable rotation
enableTooltips: true
}
);
// Create rotatable tube
var tube = tb.tube({
geometry: spiralPath,
radius: 0.8,
sides: 8,
color: '#00ffff',
material: 'MeshPhysicalMaterial'
});
tube.setCoords(origin);
tube.addTooltip("Alt+Drag to rotate", true);
tb.add(tube);
Raycasting
Raycasting enables precise object selection and interaction:map.on('style.load', function() {
map.addLayer({
id: 'custom_layer',
type: 'custom',
renderingMode: '3d',
onAdd: function(map, mbxContext) {
window.tb = new Threebox(
map,
mbxContext,
{
defaultLights: true,
enableSelectingFeatures: true, // Buildings
enableSelectingObjects: true, // 3D objects
enableDraggingObjects: true,
enableRotatingObjects: true,
enableTooltips: true
}
);
// Create cube without bounding box (still raycasted)
var geometry = new THREE.BoxGeometry(30, 60, 120);
var redMaterial = new THREE.MeshPhongMaterial({
color: 0x660000,
side: THREE.DoubleSide
});
var cube = tb.Object3D({
obj: new THREE.Mesh(geometry, redMaterial),
units: 'meters',
bbox: false // No bounding box, but still selectable
}).setCoords([-122.3512, 47.6202, 0]);
cube.addTooltip("Selectable without bounding box", true);
tb.add(cube);
// Create sphere
var sphere = tb.sphere({
radius: 30,
units: 'meters',
sides: 120,
color: 'green',
material: 'MeshPhysicalMaterial'
}).setCoords([-122.34548, 47.617538, 0]);
tb.add(sphere);
// Load model
var options = {
obj: './models/soldier.glb',
type: 'gltf',
scale: 100,
units: 'meters',
rotation: { x: 90, y: 0, z: 0 },
anchor: 'center'
};
tb.loadObj(options, function(model) {
var soldier = model.setCoords([-122.3491, 47.6207, 0]);
soldier.addTooltip("This is a custom tooltip", true);
tb.add(soldier);
});
},
render: function(gl, matrix) {
tb.update();
}
});
});
Fill-Extrusion Selection
Select and interact with Mapbox fill-extrusion building layers:var minZoom = 12;
// Create building layer
function createBuildingLayer() {
return {
'id': '3d-buildings',
'source': 'composite',
'source-layer': 'building',
'filter': ['==', 'extrude', 'true'],
'type': 'fill-extrusion',
'minzoom': minZoom,
'paint': {
'fill-extrusion-color': [
'case',
['boolean', ['feature-state', 'select'], false],
"lightgreen", // Selected color
['boolean', ['feature-state', 'hover'], false],
"lightblue", // Hover color
'#aaa' // Default color
],
'fill-extrusion-height': [
'interpolate',
['linear'],
['zoom'],
minZoom,
0,
minZoom + 0.05,
['get', 'height']
],
'fill-extrusion-base': [
'interpolate',
['linear'],
['zoom'],
minZoom,
0,
minZoom + 0.05,
['get', 'min_height']
],
'fill-extrusion-opacity': 0.9
}
};
}
map.on('style.load', function() {
// Initialize Threebox with feature selection
window.tb = new Threebox(
map,
map.getCanvas().getContext('webgl'),
{
defaultLights: true,
enableSelectingFeatures: true, // Enable building selection
enableTooltips: true
}
);
// Add building layer
map.addLayer(createBuildingLayer());
// Listen for feature selection events
map.on('SelectedFeatureChange', onSelectedFeatureChange);
// Update Threebox on render
map.on('render', function() {
tb.update();
});
});
function onSelectedFeatureChange(e) {
var feature = e.detail;
if (feature && feature.state && feature.state.select) {
console.log('Building selected:', feature.id);
// Get feature center
var coords = tb.getFeatureCenter(feature, null, 0);
// Show popup
new mapboxgl.Popup({ offset: 0 })
.setLngLat([coords[0], coords[1]])
.setHTML('<strong>' + (feature.id || feature.type) + '</strong>')
.addTo(map);
// Log GeoJSON
var geoJson = {
"geometry": feature.geometry,
"type": "Feature",
"properties": feature.properties
};
console.log(JSON.stringify(geoJson, null, 2));
}
}
Mouse Events
Handle mouse interactions on objects:var sphere = tb.sphere({
radius: 30,
color: 'red',
material: 'MeshToonMaterial'
}).setCoords(origin);
// Mouse over
sphere.addEventListener('ObjectMouseOver', function(e) {
console.log('Mouse over:', e.detail.name);
sphere.setColor('yellow');
}, false);
// Mouse out
sphere.addEventListener('ObjectMouseOut', function(e) {
console.log('Mouse out:', e.detail.name);
sphere.setColor('red');
}, false);
// Selection change
sphere.addEventListener('SelectedChange', function(e) {
if (e.detail.selected) {
console.log('Selected');
} else {
console.log('Unselected');
}
}, false);
tb.add(sphere);
All Interaction Events
tb.loadObj(options, function(model) {
model.setCoords(origin);
// Selection
model.addEventListener('SelectedChange', function(e) {
console.log('Selected:', e.detail.selected);
}, false);
// Mouse events
model.addEventListener('ObjectMouseOver', function(e) {
console.log('Mouse over:', e.detail.name);
}, false);
model.addEventListener('ObjectMouseOut', function(e) {
console.log('Mouse out:', e.detail.name);
}, false);
// Dragging
model.addEventListener('ObjectDragged', function(e) {
console.log('Dragged action:', e.detail.draggedAction);
console.log('Object:', e.detail.draggedObject);
}, false);
// General changes
model.addEventListener('ObjectChanged', function(e) {
console.log('Action:', e.detail.action);
console.log('Object:', e.detail.object);
}, false);
// Wireframe toggle
model.addEventListener('Wireframed', function(e) {
console.log('Wireframe:', e.detail.wireframe);
}, false);
// Animation state
model.addEventListener('IsPlayingChanged', function(e) {
console.log('Is playing:', e.detail.isPlaying);
}, false);
tb.add(model);
});
Interactive Controls
Create UI buttons to control selected objects:<div class="controls">
<button id="wireButton" disabled>Wireframe</button>
<button id="playButton" disabled>Play</button>
<button id="deleteButton" disabled>Delete</button>
</div>
<script>
var selectedObject = null;
// Track selection
model.addEventListener('SelectedChange', function(e) {
if (e.detail.selected) {
selectedObject = e.detail;
// Enable buttons
document.getElementById('wireButton').disabled = false;
document.getElementById('playButton').disabled = !e.detail.hasDefaultAnimation;
document.getElementById('deleteButton').disabled = false;
} else {
selectedObject = null;
// Disable buttons
document.getElementById('wireButton').disabled = true;
document.getElementById('playButton').disabled = true;
document.getElementById('deleteButton').disabled = true;
}
}, false);
// Wireframe toggle
document.getElementById('wireButton').addEventListener('click', function() {
if (selectedObject) {
selectedObject.wireframe = !selectedObject.wireframe;
}
});
// Play/pause animation
document.getElementById('playButton').addEventListener('click', function() {
if (selectedObject) {
if (selectedObject.isPlaying) {
selectedObject.stop();
} else {
selectedObject.playAnimation({ animation: 1, duration: 10000 });
}
}
});
// Delete object
document.getElementById('deleteButton').addEventListener('click', function() {
if (selectedObject) {
tb.remove(selectedObject);
selectedObject = null;
}
});
</script>
Tooltips
Default Tooltips
window.tb = new Threebox(
map,
mbxContext,
{
enableTooltips: true // Enable default tooltips
}
);
var sphere = tb.sphere({
radius: 30,
color: 'green'
}).setCoords(origin);
// Add tooltip
sphere.addTooltip("This is a sphere", true);
tb.add(sphere);
Custom Tooltips
function createCustomTooltip(text) {
var div = document.createElement('div');
div.className = 'custom-tooltip';
div.innerHTML = `
<h3>${text}</h3>
<p>Click to select</p>
`;
return div;
}
model.addLabel(createCustomTooltip("Custom Label"), true);
Help Tooltips
window.tb = new Threebox(
map,
mbxContext,
{
enableTooltips: true,
enableHelpTooltips: true // Show interaction hints
}
);
// Automatically shows help when:
// - Dragging: "Shift + Drag to move"
// - Altitude: "Ctrl + Drag for altitude"
// - Rotation: "Alt + Drag to rotate"
FOV and Camera Controls
Adjust field of view dynamically:import { GUI } from 'https://threejs.org/examples/jsm/libs/lil-gui.module.min.js';
var api = {
fov: Math.atan(3 / 4) * 180 / Math.PI,
orthographic: false
};
var gui = new GUI();
// FOV slider (2.5 - 45 degrees)
gui.add(api, 'fov', 2.5, 45.0).step(0.1).onChange(function() {
tb.fov = api.fov;
});
// Orthographic camera toggle
gui.add(api, 'orthographic').name('Orthographic').onChange(function() {
tb.orthographic = api.orthographic;
tb.fov = api.fov;
});
FOV values below 2.5 degrees or above 45 degrees can cause rendering issues.
Complete Interactive Example
Here’s a full example with all interaction features:<!doctype html>
<head>
<title>Threebox Interactions</title>
<link href="https://api.mapbox.com/mapbox-gl-js/v2.2.0/mapbox-gl.css" rel="stylesheet">
<script src="https://api.mapbox.com/mapbox-gl-js/v2.2.0/mapbox-gl.js"></script>
<script src="../dist/threebox.js"></script>
<link href="../dist/threebox.css" rel="stylesheet" />
<style>
body, html { width: 100%; height: 100%; margin: 0; }
#map { width: 100%; height: 100%; }
.help {
position: absolute;
top: 10px;
left: 10px;
background: rgba(0,0,0,0.7);
color: white;
padding: 10px;
border-radius: 5px;
font-size: 12px;
}
</style>
</head>
<body>
<div id='map'></div>
<div class="help">
<strong>Interactions:</strong><br>
Click to select<br>
Shift + Drag to move<br>
Ctrl + Drag for altitude<br>
Alt + Drag to rotate
</div>
<script type="module">
mapboxgl.accessToken = 'YOUR_TOKEN';
var map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/outdoors-v11',
center: [-122.3491, 47.6207],
zoom: 16.5,
pitch: 60,
antialias: true
});
map.on('style.load', function() {
window.tb = new Threebox(
map,
map.getCanvas().getContext('webgl'),
{
defaultLights: true,
enableSelectingObjects: true,
enableDraggingObjects: true,
enableRotatingObjects: true,
enableTooltips: true,
enableHelpTooltips: true
}
);
map.addLayer({
id: 'custom_layer',
type: 'custom',
renderingMode: '3d',
onAdd: function(map, mbxContext) {
tb.altitudeStep = 1;
// Cube
var geometry = new THREE.BoxGeometry(30, 60, 120);
var cube = tb.Object3D({
obj: new THREE.Mesh(geometry, new THREE.MeshPhongMaterial({
color: 0x660000,
side: THREE.DoubleSide
})),
units: 'meters'
}).setCoords([-122.3512, 47.6202, 0]);
cube.addTooltip("Red cube", true);
tb.add(cube);
// Sphere
var sphere = tb.sphere({
radius: 30,
units: 'meters',
color: 'green',
material: 'MeshPhysicalMaterial'
}).setCoords([-122.34548, 47.617538, 0]);
sphere.addTooltip("Green sphere", true);
tb.add(sphere);
// Soldier
var options = {
obj: './models/soldier.glb',
type: 'gltf',
scale: 100,
units: 'meters',
rotation: { x: 90, y: 0, z: 0 },
anchor: 'center'
};
tb.loadObj(options, function(model) {
var soldier = model.setCoords([-122.3491, 47.6207, 0]);
soldier.addTooltip("Soldier", true);
soldier.addEventListener('SelectedChange', function(e) {
console.log('Selected:', e.detail.selected);
}, false);
soldier.addEventListener('ObjectDragged', function(e) {
console.log('Dragged:', e.detail.draggedAction);
}, false);
tb.add(soldier);
soldier.playAnimation({ animation: 1, duration: 10000 });
});
},
render: function(gl, matrix) {
tb.update();
}
});
});
</script>
</body>
Next Steps
Examples Overview
Browse all interactive examples
Animations
Combine interactions with animations
3D Models
Load interactive 3D models
API Reference
Complete event API documentation