38 lines
3.9 KiB
Markdown
38 lines
3.9 KiB
Markdown
# 移动端 Xterm.js 滚动优化总结 (Mobile Scroll Optimization)
|
||
|
||
## 1. 之前的问题现象 (The Problem)
|
||
在移动端(特别是 iOS Safari/WebKit)的 `xterm.js` 终端界面中,用户上下滑动时经常会遇到 **卡顿** 或 **突然冻结**。具体的表现是:手指在屏幕上持续滑动,但屏幕内容不再发生滚动。
|
||
|
||
## 2. 根本原因 (Root Cause)
|
||
通过在 `window` 级别捕获全局手指移动日志,我们发现了导致“屏幕结冰”的确切原因:
|
||
1. **浏览器原生手势劫持 (Native Gesture Hijacking)**:移动端浏览器(OS 级别)默认会接管屏幕上的 `touch` 事件,用于判断是否需要进行页面滚动、双击放大或边缘返回等原生手势。
|
||
2. **`touchmove` 事件被吞噬**:`xterm.js` 处于 DOM 渲染模式时,内部生成了大量的 `span` 和 `div` 节点。当手指在这些复杂的层叠节点上滑动时,系统的手势识别引擎一旦认定这是系统的原生滚动行为,就会**强制中断并停止派发 JS 层的 `touchmove` 事件**(或者派发 `touchcancel`),导致我们原有的滚动计算逻辑中途得不到坐标更新,画面即刻冻结。
|
||
|
||
## 3. 演进与精细化方案 (Evolution & Refined Solution)
|
||
单纯使用“防劫持猛药”能解决滚动断流问题,但会导致一个严重的副作用:**iOS 原生的长按文本选择功能(放大镜与高亮选区)会失效。**
|
||
为此,我们抛弃了全局霸道的 `touch-action: none` 与过早的 `preventDefault`,转而使用“动态拦截(Dynamic Interception)”组合拳:
|
||
|
||
### A. 撤销 CSS 暴力禁用,依赖 JS 动态阻止
|
||
不要在终端交互区强加 `touch-action: none !important;`。必须保留浏览器默认的触摸能力,否则底层 OS 根本不会触发“长按开启选区”的计时器。
|
||
|
||
### B. Touch Start 阶段:坚决放行 (Allow to Timer)
|
||
在 `touchstart` 甚至 `pointerdown` 阶段,**绝对不再调用 `preventDefault()`**。
|
||
- **作用**:确保手指刚摸到屏幕时,原生浏览器可以“观察”这次触屏是否可能变成一次“长按唤醒放大镜和选区”的操作。
|
||
|
||
### C. Touch Move 阶段:精准拦截与驱动 (Precise Interception)
|
||
这是最核心的一步:在持续跟手的 `touchmove` 事件中,依靠 JS 来判断到底该阻止系统,还是让渡给系统。
|
||
- **如果已经有系统选区 (`hasActiveNativeSelectionInTerminal() === true`)**:
|
||
说明用户正在拖拽选区的把手。此时代码**不拦截**,顺从原生的触摸动作,全权交由系统处理文本选取。
|
||
- **如果没有选区 (`false`)**:
|
||
说明用户大概率在普通滑动。此时马上执行 `event.preventDefault()`,**强行掐断系统的原生滚动尝试**,以防止 `touchmove` 被系统没收,并将 `dy` 直接交给我们的“自定义JS滚动”。
|
||
|
||
### D. 自定义动量滚动 (Custom Momentum JS Scroll)
|
||
依赖于不被吞噬的连续 move 事件,我们实现了高性能的滚动接管:
|
||
1. **跟手滑动**:实时计算两次触屏事件的 `dy` (delta Y),直接映射 `viewportScroller.scrollTop -= dy`,实现完全的1:1跟手。
|
||
2. **惯性衰减 (Momentum)**:记录滑动撒手 (`touchend`) 瞬间的最后速度,将其转入 `requestAnimationFrame` 驱动的动画循环中进行指数级衰减,精美模拟原本仅有 OS 层才能给到的平滑阻尼滚动感受。
|
||
|
||
## 4. 最终达成的效果 (Final Results)
|
||
凭借这种高度克制且动态的干扰策略,我们实现了“鱼与熊掌兼得”:
|
||
1. **完美原生选区**:长按仍能顺畅唤起 iOS 文本放大镜与蓝色选框。
|
||
2. **不断流的跟手滚动**:只要没有进入选区模式,手指在密集的字符网格上狂滑也不会触发浏览器原生手势劫持,配合 60fps 的 JS 帧动画达到了极致丝滑。
|
||
3. **软键盘完整兼容**:没有使用全局焦点的强行剥夺,不阻碍日常的点击输入与键盘显隐。 |