Skip to content

Animate Camera Around a Point

Continuously rotate the map camera around a fixed point to create a smooth orbiting animation.

How It Works

The animation works by repeatedly calling map.easeTo() with an incremented bearing on each animation frame using requestAnimationFrame.

Basic Usage

javascript
let rotating = false;
let animationId = null;

function rotateCamera() {
  if (!rotating) return;

  // Increment bearing by a small amount each frame
  map.easeTo({
    bearing: map.getBearing() + 0.3,
    duration: 0,
    easing: (t) => t
  });

  // Request next frame
  animationId = requestAnimationFrame(rotateCamera);
}

// Start rotation
function startRotation() {
  rotating = true;
  rotateCamera();
}

// Stop rotation
function stopRotation() {
  rotating = false;
  if (animationId) cancelAnimationFrame(animationId);
}

Customization

javascript
// Faster rotation
map.easeTo({ bearing: map.getBearing() + 1.0, duration: 0 });

// Slower rotation
map.easeTo({ bearing: map.getBearing() + 0.1, duration: 0 });

// Rotate AND change pitch for a dynamic effect
map.easeTo({
  bearing: map.getBearing() + 0.5,
  pitch: 60,
  duration: 0
});

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%; }
      .controls { margin-top: 10px; display: flex; gap: 8px; }
      button { padding: 8px 16px; color: white; border: none; border-radius: 6px; cursor: pointer; }
      #start-btn { background: #22c55e; }
      #stop-btn { background: #ef4444; display: none; }
    </style>
  </head>
  <body>
    <div id="map"></div>
    <div class="controls">
      <button id="start-btn" onclick="startRotation()">▶ Start Rotation</button>
      <button id="stop-btn" onclick="stopRotation()">⏹ Stop Rotation</button>
    </div>
    <script>
      const map = new mapmetricsgl.Map({
        container: 'map',
        style: '<StyleFile_URL_with_Token>',
        center: [2.349902, 48.852966],
        zoom: 14,
        pitch: 60,
        bearing: 0
      });

      map.addControl(new mapmetricsgl.NavigationControl(), 'top-right');

      let rotating = false;
      let animationId = null;

      function rotateCamera() {
        if (!rotating) return;
        map.easeTo({ bearing: map.getBearing() + 0.3, duration: 0, easing: t => t });
        animationId = requestAnimationFrame(rotateCamera);
      }

      function startRotation() {
        rotating = true;
        document.getElementById('start-btn').style.display = 'none';
        document.getElementById('stop-btn').style.display = 'inline-block';
        rotateCamera();
      }

      function stopRotation() {
        rotating = false;
        if (animationId) cancelAnimationFrame(animationId);
        document.getElementById('start-btn').style.display = 'inline-block';
        document.getElementById('stop-btn').style.display = 'none';
      }
    </script>
  </body>
</html>
jsx
import React, { useEffect, useRef, useState } from 'react';
import mapmetricsgl from '@mapmetrics/mapmetrics-gl';
import '@mapmetrics/mapmetrics-gl/dist/mapmetrics-gl.css';

const AnimateCameraAround = () => {
  const mapContainer = useRef(null);
  const map = useRef(null);
  const rotating = useRef(false);
  const animationId = useRef(null);
  const [isRotating, setIsRotating] = useState(false);

  useEffect(() => {
    if (map.current) return;

    map.current = new mapmetricsgl.Map({
      container: mapContainer.current,
      style: '<StyleFile_URL_with_Token>',
      center: [2.349902, 48.852966],
      zoom: 14,
      pitch: 60,
      bearing: 0
    });
    map.current.addControl(new mapmetricsgl.NavigationControl(), 'top-right');

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

  const rotateCamera = () => {
    if (!rotating.current) return;
    map.current?.easeTo({ bearing: map.current.getBearing() + 0.3, duration: 0, easing: t => t });
    animationId.current = requestAnimationFrame(rotateCamera);
  };

  const startRotation = () => {
    rotating.current = true;
    setIsRotating(true);
    rotateCamera();
  };

  const stopRotation = () => {
    rotating.current = false;
    setIsRotating(false);
    if (animationId.current) cancelAnimationFrame(animationId.current);
  };

  return (
    <div>
      <div ref={mapContainer} style={{ height: '500px', width: '100%' }} />
      <div style={{ display: 'flex', gap: '8px', marginTop: '10px' }}>
        {!isRotating ? (
          <button onClick={startRotation} style={{ padding: '8px 16px', background: '#22c55e', color: 'white', border: 'none', borderRadius: '6px', cursor: 'pointer' }}>
            ▶ Start Rotation
          </button>
        ) : (
          <button onClick={stopRotation} style={{ padding: '8px 16px', background: '#ef4444', color: 'white', border: 'none', borderRadius: '6px', cursor: 'pointer' }}>
            ⏹ Stop Rotation
          </button>
        )}
      </div>
    </div>
  );
};

export default AnimateCameraAround;

For more information, visit the MapMetrics GitHub repository.