Skip to content

Improve zoom and touchscreen experience by introducing continuous exponential scaling - #1375

Closed
Unnamed2964 wants to merge 7 commits into
railmapgen:mainfrom
Unnamed2964:fix/zoom-ux-improvements
Closed

Unnamed2964 wants to merge 7 commits into
railmapgen:mainfrom
Unnamed2964:fix/zoom-ux-improvements

Conversation

@Unnamed2964

@Unnamed2964 Unnamed2964 commented Mar 14, 2026 •

Copy link
Copy Markdown
Contributor

描述

基于 fix/touchpad-zoom(阻止浏览器默认缩放),将画布缩放体验从固定步进升级为连续指数缩放,以获得和其他常用软件类似的缩放体验,并修复触屏设备上的多指手势冲突问题。

目标是让鼠标滚轮、触控板捏合、手机双指捏合三种输入方式都获得丝滑、一致的缩放手感,同时消除触屏上误放置车站、视口闪回等 UX 问题。

主要更改

1. 连续指数缩放 (5786ba12)

  • 将缩放从固定 ±10 步进改为 currentZoom × ZOOM_BASE^(delta / sensitivity) 的连续乘法公式,在任意缩放级别下手感一致
  • 触控板捏合(ctrlKey)与鼠标滚轮使用不同灵敏度参数,适配两种设备的 deltaY 量级差异
  • 适配 Firefox 的 DOM_DELTA_LINE / DOM_DELTA_PAGE deltaMode,统一为像素等价值
  • 引入 liveZoomRef / liveMinRef,在同一帧内多次触发的滚轮事件之间保持缩放值同步,避免 React 批量更新导致中间缩放量丢失
  • 手机端捏合缩放同步改为距离比(newDist / oldDist)乘法方式
  • 提取所有魔法数字到 canvas.ts(ZOOM_BASE、ZOOM_MIN、ZOOM_MAX、WHEEL_SENSITIVITY、TOUCHPAD_SENSITIVITY 等)

2. 触屏延迟放置与多指过渡 (ccada3f1)

  • 触屏点击放置车站/杂项节点延迟 150ms(TOUCH_PLACE_DEFER_MS),第二根手指按下时取消放置,防止捏合时误创建节点
  • 径向菜单检测延迟 80ms(TOUCH_RADIAL_DEFER_MS),同理防止捏合误触
  • 引入 pointerCountRef 追踪活跃指针数量,第二根手指按下时中断背景拖拽并通过 lastDragViewBoxMinRef 提交当前视觉位置,防止视口闪回到拖拽前的状态

3. 缩放阻尼与单指重新接管 (46872f7b)

  • 缩放接近 ZOOM_MIN / ZOOM_MAX 边界时(ZOOM_DAMPING_RANGE = 30),逐渐压缩缩放因子产生减速感,替代原来的硬截断
  • 双指捏合结束后,剩余的单根手指自动重新接管背景拖拽,无需全部抬起再按下
  • 在 handleTouchMove 中添加安全兜底,防止多指触摸时定时器泄露

其他说明

本功能代码主要由 Claude Sonnet 4.6/Opus 4.6 辅助生成。经过人工测试,确认功能行为符合预期且与现有业务逻辑兼容。

Replace the fixed ±10 step zoom with continuous multiplicative zoom:
- Wheel: Math.pow(ZOOM_BASE, delta / sensitivity), where sensitivity
  is lower for touchpad pinch (ctrlKey) than for mouse wheel.
- Pinch: distance ratio between consecutive touch frames.

Add liveZoomRef / liveMinRef so consecutive wheel/pinch events within
the same React batch read updated values instead of the stale Redux
snapshot captured at the start of the render.

Extract magic numbers to named constants in canvas.ts:
ZOOM_BASE, ZOOM_MIN, ZOOM_MAX, WHEEL_SENSITIVITY, TOUCHPAD_SENSITIVITY,
DELTA_LINE_MULTIPLIER (40, CSS spec 1 line ≈ 40px), DELTA_PAGE_MULTIPLIER.
Wrap node placement (station/misc-node) in a deferred callback
(TOUCH_PLACE_DEFER_MS = 150ms) on touch devices so a quick second
finger for pinch-to-zoom can cancel the destructive action. Similarly
defer radial menu detection (TOUCH_RADIAL_DEFER_MS = 80ms).

Track active pointer count (pointerCountRef) to detect multi-touch
transitions in the PointerEvent layer. When a second finger arrives:
- Cancel pending deferred node placement
- Stop background drag and commit the current visual position via
  lastDragViewBoxMinRef instead of reverting to the pre-drag Redux state
- Skip drag updates while multi-touch is active

Add singleTouchTimerRef in touch-overlay to defer nearby-element lookup
so pinch gestures skip it entirely.
Add soft-clamp near ZOOM_MIN / ZOOM_MAX (ZOOM_DAMPING_RANGE = 30):
the zoom factor is progressively reduced as the zoom level approaches
a boundary, giving the user a deceleration feel instead of an abrupt
stop. Applied to both wheel zoom and pinch zoom.

Re-initiate background drag when a single touch finger remains after
a pinch ends: detect pointerType === 'touch' with pointerCountRef === 1
and active === undefined, then restart the drag from the current pointer
position. The active === undefined guard prevents duplicate dispatches
across batched React renders.

Add a safety guard in handleTouchMove to clear singleTouchTimerRef if
it is somehow still pending when multi-touch begins.
…vas jump

When a touch drag-to-pinch transition happens before any RAF fires,
lastDragViewBoxMinRef still holds a stale value from a previous drag
session. The multi-touch cancellation then commits that stale position,
causing the canvas to visibly jump.

Initialize lastDragViewBoxMinRef to the current svgViewBoxMin both when
a new drag begins (handleBackgroundDown) and when drag re-initiates after
a pinch ends (handleBackgroundMove), so the committed position is always
up-to-date.
@Unnamed2964

Copy link
Copy Markdown
Contributor Author

现在对于触摸屏还是有问题:如果其中一根手指从对象开始,那么会漂移。
原来的行为是如果一个对象被选中,那么根本不会缩放。

When multi-touch starts, handleTouchStart unconditionally dispatched
setActive(undefined), which cleared a node's active state before the
background's pointerdown guard could check it. This defeated the
node-touch guard added in the previous commit, causing canvas drift
when a finger started on a node and a second finger touched the
background.

Only clear active when no node is being interacted with
(!active || active === 'background').
@Unnamed2964

Copy link
Copy Markdown
Contributor Author

已修复

@Unnamed2964

Copy link
Copy Markdown
Contributor Author

修改后的体验改变不太好用视频演示,需要麻烦在本地运行一下

@Unnamed2964

Unnamed2964 commented Mar 20, 2026 •

Copy link
Copy Markdown
Contributor Author

现在有个合并冲突,我有空解决一下

@Unnamed2964 Unnamed2964 changed the title fix/zoom-ux-improvements Improve zoom experience by introducing continuous exponential scaling Mar 21, 2026
@Unnamed2964 Unnamed2964 changed the title Improve zoom experience by introducing continuous exponential scaling Improve zoom and touchscreen experience by introducing continuous exponential scaling Mar 21, 2026
@thekingofcity

Copy link
Copy Markdown
Member

我们先开个议题跟踪吧,差点忘了还有这个合并请求 :(

@Unnamed2964

Unnamed2964 commented Mar 22, 2026 •

Copy link
Copy Markdown
Contributor Author

我打算明天重新review一遍本pr的修改代码,以防万一


更新:烦请再等一天

@Unnamed2964

Copy link
Copy Markdown
Contributor Author

我们先开个议题跟踪吧,差点忘了还有这个合并请求 :(

让我想想该怎么写...

@Unnamed2964

Copy link
Copy Markdown
Contributor Author

抱歉,需要些时间,但是我会在合理的时间内给出结果

@Unnamed2964
Unnamed2964 marked this pull request as draft March 24, 2026 22:20
@Unnamed2964

Copy link
Copy Markdown
Contributor Author

感觉当时想的还不是很清楚,关闭这个pr了

@Unnamed2964 Unnamed2964 reopened this Jun 12, 2026
@Unnamed2964

Copy link
Copy Markdown
Contributor Author

短期内似乎不太可能继续推进,先关闭。相关说明见 railmapgen/rmg#789。

@Unnamed2964 Unnamed2964 closed this Aug 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants