Path-Based Animations
Animate objects along geographic paths usingfollowPath().
Basic Path Animation
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 }
);
// Load a truck model
var options = {
type: 'gltf',
obj: 'models/vehicles/truck.glb',
scale: 40,
units: 'meters',
anchor: 'bottom',
rotation: { x: 90, y: 90, z: 0 }
};
tb.loadObj(options, function(model) {
var truck = model.setCoords([-122.4340, 37.7353, 0]);
tb.add(truck);
// Define a path
var path = [
[-122.4340, 37.7353, 0],
[-122.4335, 37.7358, 0],
[-122.4330, 37.7360, 0],
[-122.4325, 37.7355, 0]
];
// Animate along path
truck.followPath({
path: path,
duration: 10000 // 10 seconds
});
});
},
render: function(gl, matrix) {
tb.update();
}
});
});
Animation Options
| Option | Type | Default | Description |
|---|---|---|---|
path | array | required | Array of [lng, lat, alt] coordinates |
duration | number | 1000 | Animation duration in milliseconds |
animation | number | 0 | Embedded animation index to play |
callback | function | null | Function called when animation completes |
Interactive Path Animation
Create click-to-move functionality with the Mapbox Directions API:var origin = [-122.4340, 37.7353];
var truck;
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 }
);
var options = {
type: 'gltf',
obj: 'models/vehicles/truck.glb',
scale: 40,
units: 'meters',
anchor: 'bottom',
rotation: { x: 90, y: 90, z: 0 }
};
tb.loadObj(options, function(model) {
truck = model.setCoords(origin);
truck.addEventListener('ObjectChanged', onObjectChanged, false);
tb.add(truck);
});
},
render: function(gl, matrix) {
tb.update();
}
});
});
// Click to navigate
map.on('click', function(e) {
var destination = [e.lngLat.lng, e.lngLat.lat];
travelPath(destination);
});
function travelPath(destination) {
// Request directions from Mapbox API
var url = "https://api.mapbox.com/directions/v5/mapbox/driving/" +
[origin, destination].join(';') +
"?geometries=geojson&access_token=" + mapboxgl.accessToken;
fetch(url)
.then(response => response.json())
.then(data => {
// Extract path from API response
var path = data.routes[0].geometry.coordinates;
// Animate truck along path
truck.followPath(
{
path: path,
duration: 10000
},
function() {
// Animation complete callback
console.log('Arrived at destination');
if (line) tb.remove(line);
}
);
// Add visual line showing path
var lineGeometry = path.map(coord => coord.concat([15]));
line = tb.line({
geometry: lineGeometry,
width: 5,
color: 'steelblue'
});
tb.add(line);
// Update origin for next trip
origin = destination;
});
}
function onObjectChanged(e) {
var model = e.detail.object;
var action = e.detail.action;
console.log('Object changed:', action);
}
Embedded Model Animations
Play animations that are embedded in GLB/GLTF files:var options = {
obj: 'models/soldier.glb',
type: 'gltf',
scale: 20,
units: 'meters',
rotation: { x: 90, y: 0, z: 0 },
anchor: 'center'
};
tb.loadObj(options, function(model) {
var soldier = model.setCoords([-122.4340, 37.7353, 0]);
tb.add(soldier);
// Play animation index 1 for 10 seconds
soldier.playAnimation({
animation: 1,
duration: 10000
});
});
Animation Controls
tb.loadObj(options, function(model) {
model.setCoords(origin);
tb.add(model);
// Play animation
model.playAnimation({ animation: 1, duration: 10000 });
// Stop animation
model.stop();
// Check if playing
if (model.isPlaying) {
console.log('Animation is running');
}
// Listen to animation state changes
model.addEventListener('IsPlayingChanged', function(e) {
console.log('Is playing:', e.detail.isPlaying);
}, false);
});
Combined Animations
Play both path-based and embedded animations simultaneously:var soldier;
map.on('click', function(e) {
var destination = [e.lngLat.lng, e.lngLat.lat];
navigateSoldier(destination);
});
function navigateSoldier(destination) {
var url = "https://api.mapbox.com/directions/v5/mapbox/walking/" +
[origin, destination].join(';') +
"?geometries=geojson&access_token=" + mapboxgl.accessToken;
fetch(url)
.then(response => response.json())
.then(data => {
var path = data.routes[0].geometry.coordinates;
var duration = 10000;
// Start path animation
soldier.followPath(
{
path: path,
duration: duration
},
function() {
// Stop running animation when arrived
soldier.stop();
tb.remove(line);
}
);
// Play running animation
soldier.playAnimation({
animation: 1, // Running animation
duration: duration
});
// Add path line
var lineGeometry = path.map(coord => coord.concat([15]));
line = tb.line({
geometry: lineGeometry,
width: 5,
color: 'steelblue'
});
tb.add(line);
origin = destination;
});
}
Rotation Animations
Animate object rotation:// Create a tube
var tube = tb.tube({
geometry: spiralPath,
radius: 0.8,
sides: 8,
color: '#00ffff',
material: 'MeshPhysicalMaterial'
});
tube.setCoords(origin);
tb.add(tube);
// Animate rotation over 20 seconds
tube.set({
rotation: { x: 0, y: 0, z: 11520 }, // Degrees
duration: 20000
});
Animation Events
Listen to animation-related events:tb.loadObj(options, function(model) {
model.setCoords(origin);
// Object changed (position, rotation, scale)
model.addEventListener('ObjectChanged', function(e) {
console.log('Action:', e.detail.action);
console.log('Object:', e.detail.object);
}, false);
// Animation state changed
model.addEventListener('IsPlayingChanged', function(e) {
console.log('Is playing:', e.detail.isPlaying);
}, false);
tb.add(model);
});
Custom Animation Loop
Create custom animations using the render loop:var angle = 0;
var sphere;
map.addLayer({
id: 'custom_layer',
type: 'custom',
renderingMode: '3d',
onAdd: function(map, mbxContext) {
window.tb = new Threebox(
map,
mbxContext,
{ defaultLights: true }
);
sphere = tb.sphere({
radius: 30,
color: 'red',
material: 'MeshToonMaterial',
units: 'meters'
}).setCoords([-122.4340, 37.7353, 0]);
tb.add(sphere);
},
render: function(gl, matrix) {
// Custom animation in render loop
angle += 0.01;
// Orbit animation
var radius = 50;
var x = Math.cos(angle) * radius;
var y = Math.sin(angle) * radius;
// Update position
sphere.setCoords([
-122.4340 + x * 0.0001,
37.7353 + y * 0.0001,
Math.abs(Math.sin(angle)) * 50
]);
tb.update();
}
});
Animation with UI Controls
Add interactive animation controls:<button id="playButton">Play</button>
<button id="pauseButton">Pause</button>
<button id="wireButton">Wireframe</button>
<script>
var selectedObject;
// Play/pause animation
document.getElementById('playButton').addEventListener('click', function() {
if (selectedObject) {
if (selectedObject.isPlaying) {
selectedObject.stop();
} else {
selectedObject.playAnimation({
animation: 1,
duration: 10000
});
}
}
});
// Toggle wireframe
document.getElementById('wireButton').addEventListener('click', function() {
if (selectedObject) {
selectedObject.wireframe = !selectedObject.wireframe;
}
});
// Track selected object
model.addEventListener('SelectedChange', function(e) {
if (e.detail.selected) {
selectedObject = e.detail;
} else {
selectedObject = null;
}
}, false);
</script>
Camera Following
Make the camera follow an animated object:var truck;
truck.addEventListener('ObjectChanged', function(e) {
var model = e.detail.object;
var coords = model.coordinates;
// Pan map to follow object
map.panTo([coords[0], coords[1]], {
duration: 100,
easing: t => t
});
}, false);
// Alternative: Smooth camera following
function followObject(object) {
map.on('render', function() {
if (object && object.coordinates) {
var coords = object.coordinates;
map.setCenter([coords[0], coords[1]]);
}
});
}
Fixed Zoom Animation
Animate with fixed visual scale:var options = {
obj: 'model.glb',
type: 'gltf',
scale: 20,
units: 'meters',
fixedZoom: 18 // Preserve size below this zoom
};
tb.loadObj(options, function(model) {
model.setCoords(origin);
// Object maintains visual size when zoom < 18
model.addEventListener('ObjectChanged', function(e) {
var currentZoom = map.getZoom();
if (currentZoom < 18) {
// Object scale is adjusted automatically
}
}, false);
tb.add(model);
});
Performance Optimization
Limit Animation Updates
var lastUpdate = 0;
var updateInterval = 16; // ~60fps
render: function(gl, matrix) {
var now = Date.now();
if (now - lastUpdate > updateInterval) {
tb.update();
lastUpdate = now;
}
}
Pause Animations When Not Visible
// Pause animations when layer is not visible
map.on('zoom', function() {
var zoom = map.getZoom();
var layer = map.getLayer('custom_layer');
if (zoom < 14) {
// Pause all animations
objects.forEach(obj => {
if (obj.isPlaying) obj.stop();
});
}
});
Complete Example: Animated Soldier
Here’s a complete example with click-to-move animation:<!doctype html>
<head>
<title>Animated Soldier</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%; }
#info {
position: absolute;
top: 10px;
left: 10px;
background: rgba(0,0,0,0.7);
color: white;
padding: 10px;
border-radius: 5px;
}
</style>
</head>
<body>
<div id='map'></div>
<div id='info'>Click on the map to move the soldier</div>
<script>
mapboxgl.accessToken = 'YOUR_TOKEN';
var origin = [-122.4340, 37.7353];
var soldier;
var line;
var map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/outdoors-v11',
center: origin,
zoom: 18,
pitch: 60,
antialias: true
});
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,
enableDraggingObjects: true,
enableRotatingObjects: true,
enableTooltips: true
}
);
var options = {
obj: 'models/soldier.glb',
type: 'gltf',
scale: 20,
units: 'meters',
rotation: { x: 90, y: 0, z: 0 },
anchor: 'center'
};
tb.loadObj(options, function(model) {
soldier = model.setCoords(origin);
soldier.addEventListener('ObjectChanged', onObjectChanged, false);
tb.add(soldier);
});
},
render: function(gl, matrix) {
tb.update();
}
});
});
map.on('click', function(e) {
var destination = [e.lngLat.lng, e.lngLat.lat];
travelPath(destination);
});
function travelPath(destination) {
var url = "https://api.mapbox.com/directions/v5/mapbox/walking/" +
[origin, destination].join(';') +
"?geometries=geojson&access_token=" + mapboxgl.accessToken;
fetch(url)
.then(response => response.json())
.then(data => {
var duration = 10000;
var path = data.routes[0].geometry.coordinates;
// Animate along path
soldier.followPath(
{ path: path, duration: duration },
function() {
soldier.stop();
tb.remove(line);
}
);
// Play running animation
soldier.playAnimation({ animation: 1, duration: duration });
// Show path
var lineGeometry = path.map(coord => coord.concat([15]));
line = tb.line({
geometry: lineGeometry,
width: 5,
color: 'steelblue'
});
tb.add(line);
origin = destination;
});
}
function onObjectChanged(e) {
console.log('Action:', e.detail.action);
}
</script>
</body>
Next Steps
Interactions
Add selection, dragging, and rotation
3D Models
Learn more about model loading
Examples
Browse all examples
API Reference
Animation API documentation