MapSnippets logoMapSnippets logo

Animated Data with MapLibre GL JS

MapLibre GL JS

Animate a circle layer's radius paint property over time to visualize temporal data changes with requestAnimationFrame.

progress_activityLoading map...
const map = new maplibregl.Map({
  container: 'map',
  style: 'https://api.maptiler.com/maps/dataviz-dark/style.json?key=YOUR_MAPTILER_KEY',
  center: [14.42, 50.08],
  zoom: 10
});
const points = [
  { coords: [14.42, 50.08], label: 'Prague' },
  { coords: [16.61, 49.20], label: 'Brno' },
  { coords: [18.26, 49.82], label: 'Ostrava' }
];
map.on('load', () => {
  map.addSource('pulses', { type: 'geojson', data: { type: 'FeatureCollection',
    features: points.map(p => ({ type: 'Feature', geometry: { type: 'Point', coordinates: p.coords }, properties: { label: p.label } })) } });
  map.addLayer({ id: 'pulse-ring', type: 'circle', source: 'pulses',
    paint: { 'circle-radius': 10, 'circle-color': 'transparent', 'circle-stroke-width': 2, 'circle-stroke-color': '#2196F3', 'circle-opacity': 1 } });
  map.addLayer({ id: 'pulse-center', type: 'circle', source: 'pulses',
    paint: { 'circle-radius': 5, 'circle-color': '#2196F3' } });
  let t = 0;
  function animate() {
    t = (t + 1) % 120;
    const r = 10 + (t / 120) * 40;
    const o = 1 - t / 120;
    map.setPaintProperty('pulse-ring', 'circle-radius', r);
    map.setPaintProperty('pulse-ring', 'circle-stroke-opacity', o);
    requestAnimationFrame(animate);
  }
  animate();
});

How it works

This example creates a pulsing ring animation by changing circle-radius and circle-stroke-opacity on every frame using requestAnimationFrame. A counter t cycles from 0 to 119, driving the ring outward from 10px to 50px while fading it out.

Two layers are used: a solid center dot that stays fixed, and an expanding ring that resets when it reaches maximum size. The setPaintProperty() method updates paint values in real time without re-creating the layer.

This pattern is useful for visualizing events, alerts, or temporal data like earthquake waves or signal propagation.

Key APIs

API Description Docs
map.setPaintProperty() Update a layer’s paint property maplibre.org docs
requestAnimationFrame Browser animation loop MDN docs
circle-stroke-opacity Circle border transparency maplibre.org docs

FAQ

How do I animate different points at different speeds?

Use per-feature data-driven expressions with a property that changes over time, or maintain separate layers for each animation phase.

Can I pause the animation?

Yes. Store the requestAnimationFrame ID and call cancelAnimationFrame(id) to pause. Call animate() again to resume.

Common mistakes

  • Using setInterval instead of requestAnimationFrame: rAF syncs with the display refresh rate for smoother animation
  • Not cycling the counter: the animation freezes at the maximum value without a reset
  • Updating too many paint properties per frame: keep animated properties minimal for performance
auto_awesome

Build maps using MapLibre GL JS with AI

Generate code like this animated data with maplibre gl js example using the MapLibre GL JS skill in your IDE.