> For the complete documentation index, see [llms.txt](https://docs.mapbox.com/mapbox-gl-js/ja/llms.txt)

# Animate a point along a route

Use [Turf](http://turfjs.org/) to smoothly animate a point along the distance of a line.

> Example code:

**JavaScript**

```html
<!DOCTYPE html>
<html>
<head>
<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">
<link href="https://api.mapbox.com/mapbox-gl-js/v3.28.1/mapbox-gl.css" rel="stylesheet">
<script src="https://api.mapbox.com/mapbox-gl-js/v3.28.1/mapbox-gl.js"></script>
<style>
body { margin: 0; padding: 0; }
#map { position: absolute; top: 0; bottom: 0; width: 100%; }
</style>
</head>
<body>
<style>
    .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 {
        background-color: #4ea0da;
    }

    .overlay button:disabled {
        background: #f5f5f5;
        color: #c3c3c3;
    }
</style>
<script src="https://unpkg.com/@turf/turf@6/turf.min.js"></script>

<div id="map"></div>
<div class="overlay">
    <button id="replay">Replay</button>
</div>

<script>
    const map = new mapboxgl.Map({
        // TO MAKE THE MAP APPEAR YOU MUST
        // ADD YOUR ACCESS TOKEN FROM
        // https://account.mapbox.com
        accessToken: 'YOUR_MAPBOX_ACCESS_TOKEN',
        container: 'map',
        // Choose from Mapbox's core styles, or make your own style with Mapbox Studio,
        style: 'mapbox://styles/mapbox/standard',
        config: {
            basemap: {
                theme: 'monochrome',
                lightPreset: 'night'
            }
        },
        center: [-96, 37.8],
        zoom: 3,
        pitch: 40
    });

    // San Francisco
    const origin = [-122.414, 37.776];

    // Washington DC
    const destination = [-77.032, 38.913];

    // A simple line from origin to destination.
    const route = {
        'type': 'FeatureCollection',
        'features': [
            {
                'type': 'Feature',
                'geometry': {
                    'type': 'LineString',
                    'coordinates': [origin, destination]
                }
            }
        ]
    };

    // A single point that animates along the route.
    // Coordinates are initially set to origin.
    const point = {
        'type': 'FeatureCollection',
        'features': [
            {
                'type': 'Feature',
                'properties': {},
                'geometry': {
                    'type': 'Point',
                    'coordinates': origin
                }
            }
        ]
    };

    // Calculate the distance in kilometers between route start/end point.
    const lineDistance = turf.length(route.features[0]);

    const 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
    const steps = 500;

    // Draw an arc between the `origin` & `destination` of the two points
    for (let i = 0; i < lineDistance; i += lineDistance / steps) {
        const segment = turf.along(route.features[0], i);
        arc.push(segment.geometry.coordinates);
    }

    // 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.
    let counter = 0;

    map.on('load', () => {
        // 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
        });

        map.addLayer({
            'id': 'route',
            'source': 'route',
            'type': 'line',
            'paint': {
                'line-width': 2,
                'line-color': '#007cbf',
                'line-emissive-strength': 1
            }
        });

        map.addLayer({
            'id': 'point',
            'source': 'point',
            'type': 'circle',
            'paint': {
                'circle-radius': 6,
                'circle-color': '#007cbf',
                'circle-emissive-strength': 1,
                'circle-stroke-width': 2,
                'circle-stroke-color': '#ffffff'
            }
        });
        let running = false;
        function animate() {
            running = true;
            document.getElementById('replay').disabled = true;
            const start =
                route.features[0].geometry.coordinates[
                    counter >= steps ? counter - 1 : counter
                ];
            const end =
                route.features[0].geometry.coordinates[
                    counter >= steps ? counter : counter + 1
                ];
            if (!start || !end) {
                running = false;
                document.getElementById('replay').disabled = false;
                return;
            }
            // 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 calculated between the current point and the next point, except
            // at the end of the arc, which uses the previous point and the current point
            point.features[0].properties.bearing = turf.bearing(
                turf.point(start),
                turf.point(end)
            );

            // Update the source with this new data
            map.getSource('point').setData(point);

            // Request the next frame of animation as long as the end has not been reached
            if (counter < steps) {
                requestAnimationFrame(animate);
            }

            counter = counter + 1;
        }

        document.getElementById('replay').addEventListener('click', () => {
            if (running) {
                void 0;
            } else {
                // Set the coordinates of the original point back to origin
                point.features[0].geometry.coordinates = origin;

                // Update the source layer
                map.getSource('point').setData(point);

                // Reset the counter
                counter = 0;

                // Restart the animation
                animate(counter);
            }
        });

        // Start the animation
        animate(counter);
    });
</script>

</body>
</html>
```

**React**

```jsx
import React, { useEffect, useRef, useState } from 'react';
import * as mapboxgl from 'mapbox-gl/esm';
import 'mapbox-gl/dist/mapbox-gl.css';
import * as turf from '@turf/turf';

const MapboxExample = () => {
  const mapContainerRef = useRef();
  const mapRef = useRef();
  const pointRef = useRef(null);
  const originRef = useRef(null);
  const routeRef = useRef(null);
  const [disabled, setDisabled] = useState(true);
  const steps = 500;
  let counter = 0;

  function handleClick() {
    pointRef.current.features[0].geometry.coordinates = originRef.current;
    mapRef.current.getSource('point').setData(pointRef.current);
    animate(0);
    setDisabled(true);
  }

  function animate() {
    const start =
      routeRef.current.features[0].geometry.coordinates[
        counter >= steps ? counter - 1 : counter
      ];
    const end =
      routeRef.current.features[0].geometry.coordinates[
        counter >= steps ? counter : counter + 1
      ];

    if (!start || !end) {
      setDisabled(false);
      return;
    }

    pointRef.current.features[0].geometry.coordinates =
      routeRef.current.features[0].geometry.coordinates[counter];
    pointRef.current.features[0].properties.bearing = turf.bearing(
      turf.point(start),
      turf.point(end)
    );

    mapRef.current.getSource('point').setData(pointRef.current);

    if (counter < steps) {
      requestAnimationFrame(animate);
    }

    counter = counter + 1;
  }

  useEffect(() => {
    mapRef.current = new mapboxgl.Map({
      // TO MAKE THE MAP APPEAR YOU MUST
      // ADD YOUR ACCESS TOKEN FROM
      // https://account.mapbox.com
      accessToken: 'YOUR_MAPBOX_ACCESS_TOKEN',
      container: mapContainerRef.current,
      style: 'mapbox://styles/mapbox/standard',
      config: {
        basemap: {
          theme: 'monochrome',
          lightPreset: 'night'
        }
      },
      center: [-96, 37.8],
      zoom: 3,
      pitch: 40
    });

    const origin = [-122.414, 37.776];
    originRef.current = origin;

    const destination = [-77.032, 38.913];

    const route = {
      type: 'FeatureCollection',
      features: [
        {
          type: 'Feature',
          geometry: {
            type: 'LineString',
            coordinates: [origin, destination]
          }
        }
      ]
    };
    routeRef.current = route;

    const point = {
      type: 'FeatureCollection',
      features: [
        {
          type: 'Feature',
          properties: {},
          geometry: {
            type: 'Point',
            coordinates: origin
          }
        }
      ]
    };
    pointRef.current = point;

    const lineDistance = turf.length(route.features[0]);
    const arc = [];

    for (let i = 0; i < lineDistance; i += lineDistance / steps) {
      const segment = turf.along(route.features[0], i);
      arc.push(segment.geometry.coordinates);
    }

    route.features[0].geometry.coordinates = arc;

    mapRef.current.on('load', () => {
      mapRef.current.addSource('route', {
        type: 'geojson',
        data: route
      });

      mapRef.current.addSource('point', {
        type: 'geojson',
        data: point
      });

      mapRef.current.addLayer({
        id: 'route',
        source: 'route',
        type: 'line',
        paint: {
          'line-width': 2,
          'line-color': '#007cbf',
          'line-emissive-strength': 1
        }
      });

      mapRef.current.addLayer({
        id: 'point',
        source: 'point',
        type: 'circle',
        paint: {
          'circle-radius': 6,
          'circle-color': '#007cbf',
          'circle-emissive-strength': 1,
          'circle-stroke-width': 2,
          'circle-stroke-color': '#ffffff'
        }
      });

      animate(counter);
    });

    // Cleanup function
    return () => mapRef.current.remove();
  }, []);

  return (
    <div style={{ height: '100%', position: 'relative' }}>
      <div ref={mapContainerRef} style={{ height: '100%', width: '100%' }} />
      <div
        style={{
          position: 'absolute',
          top: '10px',
          left: '10px'
        }}
      >
        <button
          disabled={disabled}
          style={{
            backgroundColor: disabled ? '#f5f5f5' : '#3386c0',
            color: disabled ? '#c3c3c3' : '#fff',
            display: 'inline-block',
            margin: '0',
            padding: '10px 20px',
            border: 'none',
            cursor: 'pointer',
            borderRadius: '3px'
          }}
          onClick={handleClick}
          id="replay"
        >
          Replay
        </button>
      </div>
    </div>
  );
};

export default MapboxExample;
```