Skip to content

Animate a Point

Move a point continuously on the map using requestAnimationFrame for smooth, frame-based animation.

How It Works

Use requestAnimationFrame to call a function on every browser repaint (~60fps). On each frame, compute the new position and call setData() to move the point.

Key Pattern

javascript
// 1. Add point source
map.addSource('point', {
  type: 'geojson',
  data: {
    type: 'Feature',
    geometry: { type: 'Point', coordinates: [0, 0] },
    properties: {}
  }
});

// 2. Display as circle layer
map.addLayer({
  id: 'point',
  type: 'circle',
  source: 'point',
  paint: { 'circle-radius': 12, 'circle-color': '#f59e0b' }
});

// 3. Animate position on each frame
let t = 0;
function animate() {
  t += 0.01;
  const lng = Math.cos(t) * 60;
  const lat = Math.sin(t) * 30;

  map.getSource('point').setData({
    type: 'Feature',
    geometry: { type: 'Point', coordinates: [lng, lat] },
    properties: {}
  });

  requestAnimationFrame(animate);
}

animate();

Stop and Resume Animation

javascript
let animId = null;
let running = false;

function start() {
  if (running) return;
  running = true;
  function tick() {
    if (!running) return;
    // update position...
    animId = requestAnimationFrame(tick);
  }
  tick();
}

function stop() {
  running = false;
  if (animId) cancelAnimationFrame(animId);
}

Complete Example

html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <link href="https://cdn.mapmetrics-atlas.net/versions/latest/mapmetrics-gl.css" rel="stylesheet" />
    <script src="https://cdn.mapmetrics-atlas.net/versions/latest/mapmetrics-gl.js"></script>
    <style>#map { height: 500px; width: 100%; }</style>
  </head>
  <body>
    <div id="map"></div>
    <script>
      const map = new mapmetricsgl.Map({
        container: 'map',
        style: '<StyleFile_URL_with_Token>',
        center: [0, 0],
        zoom: 1.5
      });

      map.on('load', () => {
        map.addSource('point', {
          type: 'geojson',
          data: { type: 'Feature', geometry: { type: 'Point', coordinates: [0, 0] }, properties: {} }
        });

        map.addLayer({
          id: 'point',
          type: 'circle',
          source: 'point',
          paint: { 'circle-radius': 12, 'circle-color': '#f59e0b', 'circle-stroke-width': 3, 'circle-stroke-color': '#fff' }
        });

        let t = 0;
        function animate() {
          t += 0.015;
          map.getSource('point').setData({
            type: 'Feature',
            geometry: { type: 'Point', coordinates: [Math.cos(t) * 60, Math.sin(t * 0.7) * 30] },
            properties: {}
          });
          requestAnimationFrame(animate);
        }
        animate();
      });
    </script>
  </body>
</html>
jsx
import React, { useEffect, useRef } from 'react';
import mapmetricsgl from '@mapmetrics/mapmetrics-gl';
import '@mapmetrics/mapmetrics-gl/dist/mapmetrics-gl.css';

const AnimatePoint = () => {
  const mapContainer = useRef(null);
  const map = useRef(null);
  const animId = useRef(null);

  useEffect(() => {
    if (map.current) return;
    map.current = new mapmetricsgl.Map({
      container: mapContainer.current,
      style: '<StyleFile_URL_with_Token>',
      center: [0, 0],
      zoom: 1.5
    });

    map.current.on('load', () => {
      map.current.addSource('point', {
        type: 'geojson',
        data: { type: 'Feature', geometry: { type: 'Point', coordinates: [0, 0] }, properties: {} }
      });

      map.current.addLayer({
        id: 'point',
        type: 'circle',
        source: 'point',
        paint: { 'circle-radius': 12, 'circle-color': '#f59e0b', 'circle-stroke-width': 3, 'circle-stroke-color': '#fff' }
      });

      let t = 0;
      const animate = () => {
        t += 0.015;
        map.current?.getSource('point')?.setData({
          type: 'Feature',
          geometry: { type: 'Point', coordinates: [Math.cos(t) * 60, Math.sin(t * 0.7) * 30] },
          properties: {}
        });
        animId.current = requestAnimationFrame(animate);
      };
      animate();
    });

    return () => {
      if (animId.current) cancelAnimationFrame(animId.current);
      map.current?.remove();
      map.current = null;
    };
  }, []);

  return <div ref={mapContainer} style={{ height: '500px', width: '100%' }} />;
};

export default AnimatePoint;

For more information, visit the MapMetrics GitHub repository.