Customize camera animations
Replicates the MapLibre example Customize camera animations. The target marker is YAML: one named source drawn by a circle layer and a label layer. The control panel is page chrome in <ml-map>’s top-left slot, and its JS turns the choices into flyTo AnimationOptions, then moves the target through updateLayerData().
The YAML half
Section titled “The YAML half”# 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/customize-camera-animations/# The YAML half: the map and the "target" marker (one named source, two# layers; the label reads a `label` property). The page's JS turns the# panel's choices into flyTo AnimationOptions and moves the target —# see customize-camera-animations.js.type: mapid: customize-camera-animationsconfig: center: [-95, 40] zoom: 3 mapStyle: "https://demotiles.maplibre.org/style.json"sources: target: type: geojson data: type: Feature properties: { label: "Center: [-94, 40]" } geometry: { type: Point, coordinates: [-94, 40] }layers: - id: target-circle type: circle source: target paint: circle-radius: 6 circle-color: "#007cbf" - id: target-label type: symbol source: target layout: text-field: ["get", "label"] text-font: ["Noto Sans Regular"] text-offset: [0, 0.6] text-anchor: topThe page half
Section titled “The page half”This markup goes inside <ml-map>; children with a slot ride its corner system.
<div slot="top-left" class="gallery-panel" data-camera-panel> <fieldset> <label>Easing <select name="easing"> <option value="easeInCubic">Ease In Cubic</option> <option value="easeOutQuint">Ease Out Quint</option> <option value="easeInOutCirc">Ease In/Out Circ</option> <option value="easeOutBounce">Ease Out Bounce</option> </select> </label> <label>Duration <input type="range" name="duration" min="0" max="10000" step="500" value="1000" /> <span data-duration-value></span> </label> <label><input type="checkbox" name="animate" checked /> Animate camera motion</label> <label>Offset X <input type="number" name="offset-x" min="-200" max="200" step="50" value="0" /></label> <label>Offset Y <input type="number" name="offset-y" min="-200" max="200" step="50" value="0" /></label> <button type="button" data-animate>Test animation</button> </fieldset></div>The JavaScript half
Section titled “The JavaScript half”// The JavaScript half: flyTo's AnimationOptions — duration, easing, offset,// animate — built from the panel's controls. The YAML's "target" layers// show where the camera was asked to centre; with an offset, compare that// point with where the camera actually ends up.const mapEl = document.querySelector("ml-map");const panel = document.querySelector("[data-camera-panel]");const field = (name) => panel.querySelector(`[name="${name}"]`);
// Easing functions map animation progress t (0..1) to output progress.const easingFunctions = { easeInCubic: (t) => t * t * t, // slow start, then speeds up easeOutQuint: (t) => 1 - Math.pow(1 - t, 5), // fast start, long wind-down easeInOutCirc: (t) => // slow start and finish, fast middle t < 0.5 ? (1 - Math.sqrt(1 - Math.pow(2 * t, 2))) / 2 : (Math.sqrt(1 - Math.pow(-2 * t + 2, 2)) + 1) / 2, easeOutBounce(t) { // fast start with a bounce at the end const n1 = 7.5625; const d1 = 2.75; if (t < 1 / d1) return n1 * t * t; if (t < 2 / d1) return n1 * (t -= 1.5 / d1) * t + 0.75; if (t < 2.5 / d1) return n1 * (t -= 2.25 / d1) * t + 0.9375; return n1 * (t -= 2.625 / d1) * t + 0.984375; },};
const durationLabel = panel.querySelector("[data-duration-value]");const showDuration = () => (durationLabel.textContent = `${field("duration").value / 1000} s`);field("duration").addEventListener("input", showDuration);showDuration();
panel.querySelector("[data-animate]").addEventListener("click", async () => { const map = await mapEl.mapReady(); // A random target up to 10 degrees from the starting centre. const center = [-95 + (Math.random() - 0.5) * 20, 40 + (Math.random() - 0.5) * 20];
map.flyTo({ center, duration: Number(field("duration").value), easing: easingFunctions[field("easing").value], offset: [Number(field("offset-x").value), Number(field("offset-y").value)], animate: field("animate").checked, essential: true, });
mapEl.updateLayerData("target-circle", { type: "Feature", properties: { label: `Center: [${center[0].toFixed(1)}, ${center[1].toFixed(1)}]` }, geometry: { type: "Point", coordinates: center }, });});What to notice
Section titled “What to notice”- With an offset, the camera centres the target that many pixels away from the viewport centre — compare where the marker lands with the middle of the map.
- Both layers read one named source, so updating it through either layer id moves the circle and its label together.
- Unchecking Animate camera motion sets
animate: false: the same call becomes an instant jump.