Smooth Transitions for MapLibre GL JS
Animate any data-driven paint property, per feature, with easing,
delays and multi-stop breakpoints. One
requestAnimationFrame for the whole map —
thousands of features, one write each per frame.
Interactive Examples
6Rising City
Five thousand real Vancouver buildings grow from their footprints to
full height in 3D. Per-building staggered
delay, two channels each — height and
color — and a camera that orbits the skyline it just built.
paint: { 'fill-extrusion-height': [null, h] }
the call
Playground
Every knob on the API, wired to a live map, printing the exact
paint object it is about to run. Nine
easings, side by side.
map.transition(f, { duration, ease, delay, paint })
breakpoints
Color & Breakpoints
An editor whose UI is the value you pass. Add, remove and reorder stops; three or more make a piecewise ramp.
'circle-color': ['#26547c', '#e9c46a', '#f4703a']
scheduler
Stress
Eight thousand points, three channels each — 24,000 animating
properties on one rAF. Turn up the stagger and watch
setFeatureState calls per frame fall roughly
fivefold, because a delayed transition costs nothing until it starts.
map.transition(f, { delay: i * 0.4 })
pointer
Hover Effects
delay as a dwell threshold: leaving
supersedes the pending transition, so it never fires. No timers, no
saved values.
map.transition(f, { delay: dwellMs })
sequence
Chained Transitions
Sequences built purely on onComplete. Stop
and re-trigger cancel by supersession — a superseded step never
re-arms.
map.transition(f, { onComplete: next })
Getting Started
Installation
npm install maplibre-transition
Quick Example
import maplibregl from 'maplibre-gl';
import MaplibreTransition from 'maplibre-transition';
const map = new maplibregl.Map({
container: 'map',
style: 'https://demotiles.maplibre.org/style.json',
center: [-123.12, 49.28],
zoom: 11
});
// Teach the map to transition. Adds map.transition().
MaplibreTransition.init(map);
map.on('click', 'cities-layer', (e) => {
map.transition(e.features[0], {
duration: 900,
ease: 'cubic',
delay: 0,
paint: {
// null = "start from wherever this feature is right now"
'circle-radius': [null, 28],
'circle-color': [null, '#f4703a']
},
onComplete: () => console.log('landed')
});
});
(layer, paint-property) pair, the plugin rewrites it to
["coalesce", ["feature-state", …], fallback] and owns it.
Animate it with map.transition() from then on — never
map.setPaintProperty().