Camera

flyTo follows the Van Wijk and Nuij zoom-and-pan path, which arcs out to a wider zoom during a long move and derives its own duration from the distance travelled: crossing a continent takes longer than nudging to a neighbouring country, without anyone tuning a number. Interpolating centre and scale linearly instead feels wrong, because perceived motion is logarithmic in scale, and that difference is what separates a camera that reads as cinematic from one that reads as a slideshow.

On a globe, a move to a place is a rotation

The camera is a screen-space transform, and no amount of translating it can reach the far side of a sphere. So on an azimuthal projection the same flyTo({ center }) turns the globe instead of panning it: this one opens facing Brazil, and India is behind the planet. The rotation interpolates as a quaternion slerp, which is what makes it take the short way round and hold a steady angular pace rather than wobbling, and its duration comes from the angle covered, so a hop to Iceland does not take as long as a flip to the antipodes. Drag it too: the gestures and the camera agree about where the globe is.

Interruptible, and retargeting

Click two flights in quick succession. The second does not queue behind the first and does not snap: it starts from wherever the camera has interpolated to. In a scroll-driven story the reader can outrun any animation at any moment, so every move has to be interruptible or the map ends up somewhere the narrative did not ask for. Panning or zooming by hand stops an in-flight move for the same reason.

map.camera.flyTo({ center: [2.35, 48.86], zoom: 8 })   // Van Wijk path, derived duration
map.camera.easeTo({ center: [2.35, 48.86], zoom: 8, duration: 400, ease: 'cubicInOut' })
map.camera.jumpTo({ zoom: 4 })
map.camera.fitBounds(worldBounds, { padding: 40, maxZoom: 12 })
await map.frameFeature('France', { padding: 40 })
await map.resetView()

// One step about the centre of the plot: what the on-screen +/- buttons and the
// +/- keys do, so host chrome behaves exactly like the built-in controls.
map.zoomIn()
map.zoomOut()

// Same call, orthographic projection: turns the sphere rather than panning it.
await globe.camera.flyTo({ center: [79, 22] })

Every one of these honours prefers-reduced-motion by jumping instead of animating, rather than by breaking.