Add an animated icon to the map
This example creates an animated pulsing dot icon and adds it to the map.
It generates the 'pulsingDot' image at runtime with the Canvas API and uses the StyleImageInterface
specification to help render the animation on every frame.
When the map loads, it uses addImage
to add 'pulsingDot' to the style as an icon-image
called 'pulsing-dot', then it uses addSource
to add a source called 'dot-point' with a single point feature at the coordinate location [0, 0]
. Lastly, it uses addLayer
to create a symbol layer called 'layer-with-pulsing-dot' that uses the 'pulsing-dot' icon to represent the 'dot-point' feature on the map.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Add an animated icon to the map</title>
<meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no">
<link href="https://api.mapbox.com/mapbox-gl-js/v3.9.1/mapbox-gl.css" rel="stylesheet">
<script src="https://api.mapbox.com/mapbox-gl-js/v3.9.1/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>
// 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',
center: [0, 0],
zoom: 2,
// Choose from Mapbox's core styles, or make your own style with Mapbox Studio
style: 'mapbox://styles/mapbox/streets-v12'
});
const size = 200;
// This implements `StyleImageInterface`
// to draw a pulsing dot icon on the map.
const pulsingDot = {
width: size,
height: size,
data: new Uint8Array(size * size * 4),
// When the layer is added to the map,
// get the rendering context for the map canvas.
onAdd: function () {
const canvas = document.createElement('canvas');
canvas.width = this.width;
canvas.height = this.height;
this.context = canvas.getContext('2d');
},
// Call once before every frame where the icon will be used.
render: function () {
const duration = 1000;
const t = (performance.now() % duration) / duration;
const radius = (size / 2) * 0.3;
const outerRadius = (size / 2) * 0.7 * t + radius;
const context = this.context;
// Draw the outer circle.
context.clearRect(0, 0, this.width, this.height);
context.beginPath();
context.arc(
this.width / 2,
this.height / 2,
outerRadius,
0,
Math.PI * 2
);
context.fillStyle = `rgba(255, 200, 200, ${1 - t})`;
context.fill();
// Draw the inner circle.
context.beginPath();
context.arc(
this.width / 2,
this.height / 2,
radius,
0,
Math.PI * 2
);
context.fillStyle = 'rgba(255, 100, 100, 1)';
context.strokeStyle = 'white';
context.lineWidth = 2 + 4 * (1 - t);
context.fill();
context.stroke();
// Update this image's data with data from the canvas.
this.data = context.getImageData(
0,
0,
this.width,
this.height
).data;
// Continuously repaint the map, resulting
// in the smooth animation of the dot.
map.triggerRepaint();
// Return `true` to let the map know that the image was updated.
return true;
}
};
map.on('load', () => {
map.addImage('pulsing-dot', pulsingDot, { pixelRatio: 2 });
map.addSource('dot-point', {
'type': 'geojson',
'data': {
'type': 'FeatureCollection',
'features': [
{
'type': 'Feature',
'geometry': {
'type': 'Point',
'coordinates': [0, 0] // icon position [lng, lat]
}
}
]
}
});
map.addLayer({
'id': 'layer-with-pulsing-dot',
'type': 'symbol',
'source': 'dot-point',
'layout': {
'icon-image': 'pulsing-dot'
}
});
});
</script>
</body>
</html>
This code snippet will not work as expected until you replace
YOUR_MAPBOX_ACCESS_TOKEN
with an access token from your Mapbox account./* eslint react/no-this-in-sfc: 0 */
import React, { useEffect, useRef } from 'react';
import mapboxgl from 'mapbox-gl';
import 'mapbox-gl/dist/mapbox-gl.css';
const MapboxExample = () => {
const mapContainerRef = useRef();
const mapRef = useRef();
useEffect(() => {
// TO MAKE THE MAP APPEAR YOU MUST
// ADD YOUR ACCESS TOKEN FROM
// https://account.mapbox.com
mapboxgl.accessToken = 'YOUR_MAPBOX_ACCESS_TOKEN';
if (mapRef.current) return;
mapRef.current = new mapboxgl.Map({
container: 'map',
center: [0, 0],
zoom: 2,
style: 'mapbox://styles/mapbox/streets-v12'
});
const size = 200;
const pulsingDot = {
width: size,
height: size,
data: new Uint8Array(size * size * 4),
onAdd: function () {
const canvas = document.createElement('canvas');
canvas.width = this.width;
canvas.height = this.height;
this.context = canvas.getContext('2d');
},
render: function () {
const duration = 1000;
const t = (performance.now() % duration) / duration;
const radius = (size / 2) * 0.3;
const outerRadius = (size / 2) * 0.7 * t + radius;
const context = this.context;
context.clearRect(0, 0, this.width, this.height);
context.beginPath();
context.arc(
this.width / 2,
this.height / 2,
outerRadius,
0,
Math.PI * 2
);
context.fillStyle = `rgba(255, 200, 200, ${1 - t})`;
context.fill();
context.beginPath();
context.arc(this.width / 2, this.height / 2, radius, 0, Math.PI * 2);
context.fillStyle = 'rgba(255, 100, 100, 1)';
context.strokeStyle = 'white';
context.lineWidth = 2 + 4 * (1 - t);
context.fill();
context.stroke();
this.data = context.getImageData(0, 0, this.width, this.height).data;
mapRef.current.triggerRepaint();
return true;
}
};
mapRef.current.on('load', () => {
mapRef.current.addImage('pulsing-dot', pulsingDot, { pixelRatio: 2 });
mapRef.current.addSource('dot-point', {
type: 'geojson',
data: {
type: 'FeatureCollection',
features: [
{
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [0, 0]
}
}
]
}
});
mapRef.current.addLayer({
id: 'layer-with-pulsing-dot',
type: 'symbol',
source: 'dot-point',
layout: {
'icon-image': 'pulsing-dot'
}
});
});
}, []);
return <div id="map" ref={mapContainerRef} style={{ height: '100%' }}></div>;
};
export default MapboxExample;
This code snippet will not work as expected until you replace
YOUR_MAPBOX_ACCESS_TOKEN
with an access token from your Mapbox account.Was this example helpful?