Mapping
Camera & movement
Four verbs, and which one fires is a statement about what changed. Every number on this page is measured from the shipping map (MapCanvas.client.vue) — change the map, change this page.
The four verbs
FlyA NEW SUBJECT in the same world — a search pick, a lot click.flyTo, zoom 18, flat, north-up, 1200 ms. The arc says "we went somewhere".
EaseThe SAME subject reframed — chrome moved, a panel opened, a mode reset.easeTo: 260 ms for padding changes, 600 ms for pitch/bearing resets and zoom floors.
FitAn AREA — a suburb, a city, a parcel's true bounds.Suburb 1400 ms · city 1600 ms capped at zoom 13 · parcel via cameraForBounds between 13.6 and 18, 900 ms.
JumpThe WORLD changed — switching council, the initial centre resolving.jumpTo, zoom 14.2, no animation. A flight across half a state is noise, not orientation.
Durations
A ladder, not a constant: the further the camera goes, the longer it takes — and nothing is instant except a world switch.
260 ms— chrome reflow: the bottom sheet settling, a panel changing the padding.600–700 ms— mode resets: back to flat/north-up, lifting to a zoom floor.900–1200 ms— subject changes: framing a parcel, flying to a lot.1400–1600 ms— area fits: a suburb, a city, an LGA.
The rules
- Every programmatic move lands flat and north-up (pitch 0, bearing 0). 2D is the working view; 3D is entered deliberately. The one exception: while a 3D envelope is up, the reframe owns tilt — forcing pitch 0 first paints a one-frame bounce.
- One ease per gesture chain. MapLibre's
easeTostops the current animation, so uncoordinated calls truncate flights. A camera-target signal (cleared onmoveend) coalesces a drawer settling and a report landing into ONE padded ease. - Skip the correcting fit when the interim frame already landed — under 0.25 zoom and 24 px apart, a second ease is a shimmer, not a correction.
- No
essentialflag, ever. Underprefers-reduced-motionMapLibre collapses the flight to a jump — the map obeys the OS, and that is the wanted behaviour, not a loss. - A zoom promise is a floor, not a jump: picking a development type at city zoom lifts the camera just enough for the fabric to exist (
zoomToAtLeast) — if the user can already see lots, their camera is left exactly where it was. - Fit padding is the chrome's geometry. Desktop parcel fits pad for the search stack (~400 px left) and the report panel (~436 px right), scaled down when padding approaches the viewport —
cameraForBoundsreturns undefined past it. Phones pad symmetrically (24–28 px): the bottom sheet's height already rides in transform padding, and MapLibre ADDS transform padding to fit padding — pass it twice and the lot is shoved off-screen.