Skip to content

Animate a line

Replicates the MapLibre example Animate a line. The line layer and its styling are YAML. The animation appends a vertex every frame and hands the whole line to <ml-map>’s updateLayerData() — the element finds the layer’s source itself, so there is no getSource(...).setData() bookkeeping. The Pause button rides a corner slot.

The line draws itself around the world; Pause freezes it
animate-a-line.yaml
# yaml-language-server: $schema=https://docs.maplibre-yaml.org/schema/latest/map.schema.json
# Gallery twin of https://maplibre.org/maplibre-gl-js/docs/examples/animate-a-line/
# The YAML half: the line layer and its styling, starting as a stub. The
# animation loop grows it through <ml-map>'s updateLayerData() — see
# animate-a-line.js.
type: map
id: animate-a-line
config:
center: [0, 0]
zoom: 0.5
mapStyle: "https://demotiles.maplibre.org/style.json"
layers:
- id: line-animation
type: line
source:
type: geojson
route:
coordinates: [[0, 0], [0.5, 0.2]]
layout:
line-cap: round
line-join: round
paint:
line-color: "#ed6498"
line-width: 5
line-opacity: 0.8

This markup goes inside <ml-map>; children with a slot ride its corner system.

animate-a-line.html
<div slot="top-left" class="gallery-panel">
<button type="button" data-pause>Pause</button>
</div>
animate-a-line.js
// The JavaScript half: a requestAnimationFrame loop feeding <ml-map>'s
// updateLayerData(). Each frame appends a vertex of a sine wave and hands
// the whole line to the layer — no getSource(...).setData bookkeeping.
const mapEl = document.querySelector("ml-map");
const pauseButton = document.querySelector("[data-pause]");
const speedFactor = 30; // frames per degree of longitude
let coordinates = [[0, 0]];
let startTime = 0;
let progress = 0;
let resumed = true; // restart the clock from `progress` on the next frame
let frame = 0;
function animateLine(timestamp) {
if (resumed) {
startTime = timestamp - progress;
resumed = false;
}
progress = timestamp - startTime;
if (progress > speedFactor * 360) {
// Finished a loop: start over.
startTime = timestamp;
progress = 0;
coordinates = [[0, 0]];
} else {
const x = progress / speedFactor;
const y = Math.sin((x * Math.PI) / 90) * 40;
coordinates.push([x, y]);
mapEl.updateLayerData("line-animation", {
type: "Feature",
properties: {},
geometry: { type: "LineString", coordinates },
});
}
frame = requestAnimationFrame(animateLine);
}
mapEl.mapReady().then(() => {
frame = requestAnimationFrame(animateLine);
pauseButton.addEventListener("click", () => {
const paused = pauseButton.toggleAttribute("aria-pressed");
pauseButton.textContent = paused ? "Play" : "Pause";
if (paused) {
cancelAnimationFrame(frame);
} else {
resumed = true;
frame = requestAnimationFrame(animateLine);
}
});
});
  • updateLayerData(layerId, geojson) targets a layer; YAML authors never name the internal source the layer was given.
  • For data that arrives from a server rather than a loop, prefer the declarative refresh: or stream: source options — see Add live realtime data.