Skip to main content

Animate the camera along a path

Use the FreeCamera API to follow a path over 3D terrain.

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Animate the camera along a path</title>
<meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no">
<link href="https://api.mapbox.com/mapbox-gl-js/v3.2.0/mapbox-gl.css" rel="stylesheet">
<script src="https://api.mapbox.com/mapbox-gl-js/v3.2.0/mapbox-gl.js"></script>
<style>
body { margin: 0; padding: 0; }
#map { position: absolute; top: 0; bottom: 0; width: 100%; }
</style>
</head>
<body>
<div id="map"></div>

<script src="https://docs.mapbox.com/mapbox-gl-js/assets/routes.js"></script>
<script src="https://unpkg.com/@turf/turf@6/turf.min.js"></script>
<script>
// TO MAKE THE MAP APPEAR YOU MUST
// ADD YOUR ACCESS TOKEN FROM
// https://account.mapbox.com
mapboxgl.accessToken = 'YOUR_MAPBOX_ACCESS_TOKEN';
const map = new mapboxgl.Map({
container: 'map',
zoom: 11.53,
center: [6.5615, 46.0598],
pitch: 65,
bearing: -180,
// Choose from Mapbox's core styles, or make your own style with Mapbox Studio
style: 'mapbox://styles/mapbox/outdoors-v12',
interactive: false
});

// `routes` comes from https://docs.mapbox.com/mapbox-gl-js/assets/routes.js,
// which has properties that are in the shape of an array of arrays that correspond
// to the `coordinates` property of a GeoJSON linestring, for example:
// [
// [6.56158, 46.05989],
// [6.56913, 46.05679],
// ...
// ]
// this is the path the camera will look at
const targetRoute = routes.target;
// this is the path the camera will move along
const cameraRoute = routes.camera;

// add terrain, sky, and line layers once the style has loaded
map.on('style.load', () => {
map.addSource('mapbox-dem', {
'type': 'raster-dem',
'url': 'mapbox://mapbox.mapbox-terrain-dem-v1',
'tileSize': 512,
'maxzoom': 14
});
map.setTerrain({ 'source': 'mapbox-dem', 'exaggeration': 1.5 });
map.addSource('trace', {
type: 'geojson',
data: {
'type': 'Feature',
'properties': {},
'geometry': {
'type': 'LineString',
'coordinates': targetRoute
}
}
});
map.addLayer({
type: 'line',
source: 'trace',
id: 'line',
paint: {
'line-color': 'black',
'line-width': 5
},
layout: {
'line-cap': 'round',
'line-join': 'round'
}
});
});

// wait for the terrain and sky to load before starting animation
map.on('load', () => {
const animationDuration = 80000;
const cameraAltitude = 4000;
// get the overall distance of each route so we can interpolate along them
const routeDistance = turf.lineDistance(turf.lineString(targetRoute));
const cameraRouteDistance = turf.lineDistance(
turf.lineString(cameraRoute)
);

let start;

function frame(time) {
if (!start) start = time;
// phase determines how far through the animation we are
const phase = (time - start) / animationDuration;

// phase is normalized between 0 and 1
// when the animation is finished, reset start to loop the animation
if (phase > 1) {
// wait 1.5 seconds before looping
setTimeout(() => {
start = 0.0;
}, 1500);
}

// use the phase to get a point that is the appropriate distance along the route
// this approach syncs the camera and route positions ensuring they move
// at roughly equal rates even if they don't contain the same number of points
const alongRoute = turf.along(
turf.lineString(targetRoute),
routeDistance * phase
).geometry.coordinates;

const alongCamera = turf.along(
turf.lineString(cameraRoute),
cameraRouteDistance * phase
).geometry.coordinates;

const camera = map.getFreeCameraOptions();

// set the position and altitude of the camera
camera.position = mapboxgl.MercatorCoordinate.fromLngLat(
{
lng: alongCamera[0],
lat: alongCamera[1]
},
cameraAltitude
);

// tell the camera to look at a point along the route
camera.lookAtPoint({
lng: alongRoute[0],
lat: alongRoute[1]
});

map.setFreeCameraOptions(camera);

window.requestAnimationFrame(frame);
}

window.requestAnimationFrame(frame);
});
</script>

</body>
</html>
Was this example helpful?