Animated Route
Progressively draw a route on the map using requestAnimationFrame and dynamic GeoJSON updates.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script src="https://cdn.smartmaps.cloud/packages/smartmaps/smartmaps-gl/v2/umd/smartmaps-gl.min.js"></script>
<style>
body { margin: 0; padding: 0; }
html, body, #map { height: 100%; }
#controls {
position: absolute;
top: 20px;
left: 50%;
transform: translateX(-50%);
z-index: 1;
display: flex;
gap: 8px;
}
button {
background: #18345c;
color: white;
border: none;
padding: 10px 18px;
border-radius: 6px;
font-size: 13px;
cursor: pointer;
font-weight: 500;
}
button:hover { background: #2c5282; }
button:disabled { background: #9ca3af; cursor: not-allowed; }
</style>
</head>
<body>
<div id="map"></div>
<div id="controls">
<button id="start-btn" onclick="startAnimation()">Start animation</button>
<button id="reset-btn" onclick="resetAnimation()" disabled>Reset</button>
</div>
<script>
const map = new smartmapsgl.Map({
apiKey: "[INSERT API-KEY]",
container: "map",
center: { lat: 50.5, lng: 10.0 },
zoom: 6,
style: smartmapsgl.MapStyle.ESSENTIAL
});
// Route coordinates (Rhine river cities)
const routeCoords = [
[6.9603, 50.9375], // Cologne
[7.0986, 50.7374], // Bonn
[7.5886, 50.3569], // Koblenz
[8.2473, 50.0012], // Mainz
[8.4037, 49.4875], // Mannheim
[8.6821, 49.0069], // Karlsruhe
[7.5886, 47.5596] // Basel
];
let animationId = null;
let currentIndex = 0;
map.on("load", () => {
// Full route (dashed preview)
map.addSource("full-route", {
type: "geojson",
data: { type: "Feature", geometry: { type: "LineString", coordinates: routeCoords } }
});
map.addLayer({
id: "full-route-line",
type: "line",
source: "full-route",
layout: { "line-join": "round", "line-cap": "round" },
paint: { "line-color": "#18345c", "line-width": 4, "line-opacity": 0.2, "line-dasharray": [2, 2] }
});
// Animated route (grows over time)
map.addSource("animated-route", {
type: "geojson",
data: { type: "Feature", geometry: { type: "LineString", coordinates: [routeCoords[0]] } }
});
map.addLayer({
id: "animated-route-line",
type: "line",
source: "animated-route",
layout: { "line-join": "round", "line-cap": "round" },
paint: { "line-color": "#f6b80c", "line-width": 5 }
});
// Fit to route
const bounds = new smartmapsgl.LngLatBounds();
routeCoords.forEach(c => bounds.extend(c));
map.fitBounds(bounds, { padding: 50 });
});
function interpolate(start, end, t) {
return [
start[0] + (end[0] - start[0]) * t,
start[1] + (end[1] - start[1]) * t
];
}
function startAnimation() {
document.getElementById("start-btn").disabled = true;
document.getElementById("reset-btn").disabled = false;
currentIndex = 0;
const totalSteps = 200;
let step = 0;
const points = [routeCoords[0]];
function animate() {
step++;
const progress = step / totalSteps;
const segmentFloat = progress * (routeCoords.length - 1);
const segIndex = Math.floor(segmentFloat);
const segProgress = segmentFloat - segIndex;
if (segIndex < routeCoords.length - 1) {
const point = interpolate(routeCoords[segIndex], routeCoords[segIndex + 1], segProgress);
points.push(point);
map.getSource("animated-route").setData({
type: "Feature",
geometry: { type: "LineString", coordinates: [...points] }
});
}
if (step < totalSteps) {
animationId = requestAnimationFrame(animate);
} else {
document.getElementById("start-btn").disabled = false;
}
}
animationId = requestAnimationFrame(animate);
}
function resetAnimation() {
if (animationId) cancelAnimationFrame(animationId);
map.getSource("animated-route").setData({
type: "Feature",
geometry: { type: "LineString", coordinates: [routeCoords[0]] }
});
document.getElementById("start-btn").disabled = false;
document.getElementById("reset-btn").disabled = true;
}
</script>
</body>
</html>