fix(web): scroll-wheel zoom — overscale detent + clean floor stop#13
Merged
Conversation
Replace MapLibre's native scrollZoom with a dedicated WheelZoom handler so
the two overscale limits feel right when zooming with the wheel/trackpad:
- Overscale detent: zoom-IN stops dead at the onset of overscale — the zoom
whose displayed scale equals the covering chart's compilation scale
(coverScale), the same boundary the HUD's "Overscale xN" warning uses. The
whole scroll AND its momentum tail are absorbed, so a "throw" can't punch
through; you continue into overscale by scrolling again (a fresh gesture
releases it). Re-arms once you zoom back below.
- Floor: the 1:MIN_DETAIL_SCALE scale floor is computed live per wheel event
(getFloor) instead of read from map.getMaxZoom() — which is only re-applied
on moveend, so mid-gesture it could be stale-high and let you sail past the
floor and bounce back ~a second later on moveend. Now it stops dead at the
real max, with a small elastic over-pull that springs back in ~one frame
(latched, so a momentum tail can't hold the spring-back open).
Speed: each wheel step accumulates onto a private target and applies instantly
(duration 0). Reading map.getZoom() instead crawled — easeTo({duration:0})
only updates the camera next frame, so multiple events per frame collapsed to
one step.
HUD updateZoomCap no longer slams the band cap into setMaxZoom (the hard clamp
that caused both the sticky feel and the bounce); it records the overscale-
onset zoom as a detent exposed via getDetentZoom(). _applyScaleFloor sets
maxZoom = floor + FLOOR_GIVE for the elastic headroom. own-ship drops its own
wheel takeover and just registers a follow anchor with WheelZoom, so vessel-
anchored zoom and the detent/floor behavior share one handler.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…g it WheelZoom no longer exposes a mutable setAnchorProvider() that plugins reach in and call (single-slot, last-writer-wins, and it coupled own-ship to WheelZoom's API). Anchoring is now an injected accessor — getAnchor() — symmetric with getDetent/getFloor: WheelZoom just asks where to anchor. The shell is the broker. ChartPlotter._zoomAnchor() composes the anchor from the plugins that can contribute one (first non-null wins; null → cursor), and hands that to WheelZoom. own-ship drops its WheelZoom dependency entirely and instead exposes a zoomAnchor() capability (vessel while following a fix, else null) that the shell reads. Future camera plugins slot into the broker without WheelZoom changing, and multiple contributors compose instead of clobbering. Behavior unchanged; verified the injected path end to end. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Replace MapLibre's native scrollZoom with a dedicated WheelZoom handler so the two overscale limits feel right when zooming with the wheel/trackpad:
Overscale detent: zoom-IN stops dead at the onset of overscale — the zoom whose displayed scale equals the covering chart's compilation scale (coverScale), the same boundary the HUD's "Overscale xN" warning uses. The whole scroll AND its momentum tail are absorbed, so a "throw" can't punch through; you continue into overscale by scrolling again (a fresh gesture releases it). Re-arms once you zoom back below.
Floor: the 1:MIN_DETAIL_SCALE scale floor is computed live per wheel event (getFloor) instead of read from map.getMaxZoom() — which is only re-applied on moveend, so mid-gesture it could be stale-high and let you sail past the floor and bounce back ~a second later on moveend. Now it stops dead at the real max, with a small elastic over-pull that springs back in ~one frame (latched, so a momentum tail can't hold the spring-back open).
Speed: each wheel step accumulates onto a private target and applies instantly (duration 0). Reading map.getZoom() instead crawled — easeTo({duration:0}) only updates the camera next frame, so multiple events per frame collapsed to one step.
HUD updateZoomCap no longer slams the band cap into setMaxZoom (the hard clamp that caused both the sticky feel and the bounce); it records the overscale- onset zoom as a detent exposed via getDetentZoom(). _applyScaleFloor sets maxZoom = floor + FLOOR_GIVE for the elastic headroom. own-ship drops its own wheel takeover and just registers a follow anchor with WheelZoom, so vessel- anchored zoom and the detent/floor behavior share one handler.