Use Viewport
Pan and zoom, shared across an axis.
Preview
The two surfaces above use different axis names, so they move independently. Give them the same name and they lock together.
About
Pan and zoom existed in two of forty viewers — not because the rest didn't want it, but because the plumbing is fiddly (non-passive wheel, pointer capture, zoom anchored under the cursor, clamping that doesn't stick at the edges) and nobody wanted to write it a third time. The larger problem was that where it existed it was private: coordinate-link shared which residue was selected but not which window was being looked at, so an ideogram above a track viewer could not follow it. This writes through a viewport channel on the substrate, so every viewer passing the same axis string moves together, and an overview strip opts out with linked: false to draw its locator box instead.
Usage
import { useViewport } from "@/components/ui/use-viewport"
const vp = useViewport({ axis: "chr7", bounds: [0, 159_345_973] })
<div ref={vp.ref} {...vp.gestureProps} tabIndex={0}>…</div>components/ui/use-viewport.ts