Skip to content

Animate a Line in Maptoolkit Maps JS

To animate a line drawing itself across the map, you maintain an array of coordinates and add one point per animation frame before calling setData on the GeoJSON source. Each requestAnimationFrame call extends the line by one step until the full path is drawn. Use this technique to replay a recorded route, show a network spreading, or visualize a path being traced in real time.

const API_KEY = 'YOUR_API_KEY';

    // Build a sine-wave path across Austria
    const steps = 500;
    const coords = [];
    for (let i = 0; i <= steps; i++) {
        const t = i / steps;
        const lng = 9.5 + t * 8;
        const lat = 47.5 + Math.sin(t * Math.PI * 4) * 1.0;
        coords.push([lng, lat]);
    }

    const map = new maptoolkit.Map({
        container: 'map',
        apiKey: API_KEY,
        style: `https://styles.maptoolkit.net/maptoolkit/maptoolkit.summer.json?api_key=${API_KEY}`,
        center: [13.5, 47.5],
        zoom: 6,
        attributionControl: { compact: false }
    });

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

    const geojson = { type: 'Feature', geometry: { type: 'LineString', coordinates: [] } };
    const DRAW_DURATION = 5000; // draw the full line over 5s, frame-rate independent
    let animId;

    function runAnimation() {
        cancelAnimationFrame(animId);
        let startTime;
        function frame(now) {
            if (startTime === undefined) startTime = now;
            const t = Math.min((now - startTime) / DRAW_DURATION, 1);
            const count = Math.max(1, Math.floor(t * coords.length));
            geojson.geometry.coordinates = coords.slice(0, count);
            map.getSource('line').setData(geojson);
            if (t < 1) animId = requestAnimationFrame(frame);
        }
        animId = requestAnimationFrame(frame);
    }

    map.on('load', () => {
        map.addSource('line', { type: 'geojson', data: geojson });

        map.addLayer({
            id: 'line-animation',
            type: 'line',
            source: 'line',
            paint: { 'line-color': '#ee8a65', 'line-width': 3 }
        });

        runAnimation();
    });

    document.getElementById('replay').addEventListener('click', runAnimation);
<!DOCTYPE html>
<html lang="en">
<head>
    <title>Animate a Line - Maptoolkit Maps JS</title>
    <meta property="og:description" content="Animate a line by progressively adding coordinates to a GeoJSON source." />
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <script src="https://unpkg.com/@maptoolkit/maps@11.0.0-beta.3/dist/maptoolkit.js"></script>
    <link rel="stylesheet" href="https://unpkg.com/@maptoolkit/maps@11.0.0-beta.3/dist/maptoolkit.css" />
    <style>
        html, body { width: 100%; height: 100%; margin: 0; padding: 0; }
        #map { width: 100%; height: 100%; }
        #replay {
            position: absolute;
            top: 10px;
            left: 10px;
            padding: 10px 20px;
            background: #ee8a65;
            color: white;
            border: none;
            border-radius: 3px;
            cursor: pointer;
            font-size: 14px;
        }
    </style>
</head>
<body>
<div id="map"></div>
<button id="replay">Replay</button>
<script>
    const API_KEY = 'YOUR_API_KEY';

    // Build a sine-wave path across Austria
    const steps = 500;
    const coords = [];
    for (let i = 0; i <= steps; i++) {
        const t = i / steps;
        const lng = 9.5 + t * 8;
        const lat = 47.5 + Math.sin(t * Math.PI * 4) * 1.0;
        coords.push([lng, lat]);
    }

    const map = new maptoolkit.Map({
        container: 'map',
        apiKey: API_KEY,
        style: `https://styles.maptoolkit.net/maptoolkit/maptoolkit.summer.json?api_key=${API_KEY}`,
        center: [13.5, 47.5],
        zoom: 6,
        attributionControl: { compact: false }
    });

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

    const geojson = { type: 'Feature', geometry: { type: 'LineString', coordinates: [] } };
    const DRAW_DURATION = 5000; // draw the full line over 5s, frame-rate independent
    let animId;

    function runAnimation() {
        cancelAnimationFrame(animId);
        let startTime;
        function frame(now) {
            if (startTime === undefined) startTime = now;
            const t = Math.min((now - startTime) / DRAW_DURATION, 1);
            const count = Math.max(1, Math.floor(t * coords.length));
            geojson.geometry.coordinates = coords.slice(0, count);
            map.getSource('line').setData(geojson);
            if (t < 1) animId = requestAnimationFrame(frame);
        }
        animId = requestAnimationFrame(frame);
    }

    map.on('load', () => {
        map.addSource('line', { type: 'geojson', data: geojson });

        map.addLayer({
            id: 'line-animation',
            type: 'line',
            source: 'line',
            paint: { 'line-color': '#ee8a65', 'line-width': 3 }
        });

        runAnimation();
    });

    document.getElementById('replay').addEventListener('click', runAnimation);
</script>
</body>
</html>

Use the prompt below with any LLM to get the same result. Make sure the Maptoolkit MCP server is connected first — check out AI Integration & MCP to get started.

Use the Maptoolkit Connector. Create an interactive map with zoom level 6, centered around [13.5, 47.5]. Animate a sine-wave line drawing progressively across Austria, with a replay button.

How it works

The line grows by one coordinate per frame. An array accumulates points, the LineString is rebuilt from everything so far, and setData pushes it to the source.

The cost grows with the line. Every frame replaces a payload one vertex longer than the last, so a long route becomes noticeably more expensive toward the end. For long lines, adding several vertices per frame, or animating line-gradient over a static line instead, keeps it flat.

requestAnimationFrame is the right timer here, not setInterval. It runs in step with the browser’s paint cycle, so frames are not computed and then thrown away, and it pauses automatically when the tab is hidden.

Because the whole line is replaced each time, styling stays put: the layer never changes, only its data.

Next steps

A line drawing itself is a device for explaining something, so the next step is what it explains: a route being described, a journey through time, a connection between two places. Pacing it to the accompanying text matters more than the animation itself.

The Routing API supplies a real route to draw, and a point travelling at the leading edge is a small addition that makes the direction unmistakable.