Layers (31)
Display buildings in 3DAdd a 3D modelExtrude polygons for 3D indoor mappingAnimate a lineAnimate a series of imagesAdjust a layer's opacityAnimate a pointChange building color based on zoom levelChange the case of labelsDisplay HTML clusters with custom propertiesCreate and style clustersChange a layer's color with buttonsAdd a custom style layerStyle circles with a data-driven propertyStyle lines with a data-driven propertyDisplay and style rich text labelsAdd a pattern to a polygonAdd a new layer below labelsAdd a GeoJSON lineDraw GeoJSON pointsAdd a GeoJSON polygonCreate a heatmap layerCreate a gradient line using an expressionAdd multiple geometries from one GeoJSON sourceStyle ocean depth dataShow and hide layersChange worldview of administrative boundariesUpdate a choropleth layer by zoom levelVariable label placementVisualize population densityAdd hillshading

Animate a point along a route

Use Turf to smoothly animate a point along the distance of a line.

Mapbox GL unsupported
Mapbox GL requires WebGL support. Please check that you are using a supported browser and that WebGL is enabled.
<!DOCTYPE html>
<meta charset='utf-8' />
<title>Animate a point along a route</title>
<meta name='viewport' content='initial-scale=1,maximum-scale=1,user-scalable=no' />
<script src='https://api.tiles.mapbox.com/mapbox-gl-js/v1.0.0/mapbox-gl.js'></script>
<link href='https://api.tiles.mapbox.com/mapbox-gl-js/v1.0.0/mapbox-gl.css' rel='stylesheet' />
body { margin:0; padding:0; }
#map { position:absolute; top:0; bottom:0; width:100%; }
.overlay {
position: absolute;
top: 10px;
left: 10px;
.overlay button {
font:600 12px/20px 'Helvetica Neue', Arial, Helvetica, sans-serif;
background-color: #3386c0;
color: #fff;
display: inline-block;
margin: 0;
padding: 10px 20px;
border: none;
cursor: pointer;
border-radius: 3px;
.overlay button:hover {
<script src='https://api.tiles.mapbox.com/mapbox.js/plugins/turf/v2.0.0/turf.min.js' charset='utf-8'></script>
<div id='map'></div>
<div class='overlay'>
<button id='replay'>Replay</button>
mapboxgl.accessToken = '<your access token here>';
var map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/streets-v11',
center: [-96, 37.8],
zoom: 3
// San Francisco
var origin = [-122.414, 37.776];
// Washington DC
var destination = [-77.032, 38.913];
// A simple line from origin to destination.
var route = {
"type": "FeatureCollection",
"features": [{
"type": "Feature",
"geometry": {
"type": "LineString",
"coordinates": [
// A single point that animates along the route.
// Coordinates are initially set to origin.
var point = {
"type": "FeatureCollection",
"features": [{
"type": "Feature",
"properties": {},
"geometry": {
"type": "Point",
"coordinates": origin
// Calculate the distance in kilometers between route start/end point.
var lineDistance = turf.lineDistance(route.features[0], 'kilometers');
var arc = [];
// Number of steps to use in the arc and animation, more steps means
// a smoother arc and animation, but too many steps will result in a
// low frame rate
var steps = 500;
// Draw an arc between the `origin` & `destination` of the two points
for (var i = 0; i < lineDistance; i += lineDistance / steps) {
var segment = turf.along(route.features[0], i, 'kilometers');
// Update the route with calculated arc coordinates
route.features[0].geometry.coordinates = arc;
// Used to increment the value of the point measurement against the route.
var counter = 0;
map.on('load', function () {
// Add a source and layer displaying a point which will be animated in a circle.
map.addSource('route', {
"type": "geojson",
"data": route
map.addSource('point', {
"type": "geojson",
"data": point
"id": "route",
"source": "route",
"type": "line",
"paint": {
"line-width": 2,
"line-color": "#007cbf"
"id": "point",
"source": "point",
"type": "symbol",
"layout": {
"icon-image": "airport-15",
"icon-rotate": ["get", "bearing"],
"icon-rotation-alignment": "map",
"icon-allow-overlap": true,
"icon-ignore-placement": true
function animate() {
// Update point geometry to a new position based on counter denoting
// the index to access the arc.
point.features[0].geometry.coordinates = route.features[0].geometry.coordinates[counter];
// Calculate the bearing to ensure the icon is rotated to match the route arc
// The bearing is calculate between the current point and the next point, except
// at the end of the arc use the previous point and the current point
point.features[0].properties.bearing = turf.bearing(
turf.point(route.features[0].geometry.coordinates[counter >= steps ? counter - 1 : counter]),
turf.point(route.features[0].geometry.coordinates[counter >= steps ? counter : counter + 1])
// Update the source with this new data.
// Request the next frame of animation so long the end has not been reached.
if (counter < steps) {
counter = counter + 1;
document.getElementById('replay').addEventListener('click', function() {
// Set the coordinates of the original point back to origin
point.features[0].geometry.coordinates = origin;
// Update the source layer
// Reset the counter
counter = 0;
// Restart the animation.
// Start the animation.