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.
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.