高频手势缩放不要走 React 状态
一、掉帧的五个叠加原因
- 每次捏合都
setUserZoom—— pinch 会连续触发大量wheel事件,每个事件都进 React state,触发整棵预览重新 render。 CSS zoom不是 GPU 合成层动画 ——zoom会触发布局重算与重绘;transform: scale()多数情况可以走 compositor。- 缩放同时改外壳宽度 ——
zoomedWidth = A4_WIDTH_PX * effectiveZoom每次变化都会让外层滚动区的scrollWidth重算。 - 预览 DOM 被重复渲染 —— 隐藏测量容器 + 可见分页,缩放 state 变化会把它们一起走一遍。
- 没有
requestAnimationFrame节流 ——wheel频率可能高于屏幕刷新率,事件来一次更新一次,主线程被打满。
真正的组合是:CSS zoom + React state 高频更新 + 大 DOM 重排。
二、可迁移的判断
- 高频连续手势(pinch / drag / resize)不该驱动框架状态,应该直接写 DOM 样式;框架状态只留「最终值」。
- 优化方向不是换掉
zoom,而是把高频路径移出渲染链路——保留zoom的纵向滚动优势(transform: scale不改变布局尺寸,滚动条行为会变)。 - 定位这类问题时,先问「这个值变一次,要重排多大的 DOM」,而不是先怀疑事件监听写错了。
三、跨平台适配:判断修饰键语义,不判断系统
缩放逻辑里写 if (!e.ctrlKey && !e.metaKey) return;,就同时覆盖了 macOS(Cmd + 滚轮)和 Windows(Ctrl + 滚轮、Precision Touchpad 捏合在 Chrome/Edge 里通常也表现为 ctrlKey + wheel)。
不要按操作系统分支——按输入语义(修饰键)判断,能力自然跨平台。没适配的部分要明说:触屏 pinch 需要额外的 pointer/touch 手势逻辑,当前没有。
相关
- flex子项的百分比max-height不可靠 —— 同类:CSS 机制本身决定了代价
- 进度条要由真实进度驱动 —— 高频 UI 更新的另一种状态设计