滚动卡顿根因是主线程被>50ms长任务阻塞,performance面板火焰图可直观定位;需真机或开启触摸模拟录制足够时长的滚动,重点排查main线程中script evaluation类黄色/红色长条及强制同步布局api。

滚动卡顿的根因几乎总是主线程被长时间占用,而 Chrome DevTools 的 Performance 面板中“帧记录”(即录制后的火焰图)是唯一直观暴露这个阻塞源的方式——它不靠猜,只看谁占了 >50ms 的连续时间块。
录制时必须触发真实滚动行为,不能只靠鼠标滚轮
很多开发者在桌面端用鼠标滚轮快速滑动,但这种方式不会触发移动端常见的 touchstart → touchmove → touchend 完整事件流,iScroll 或原生 overscroll-behavior 的逻辑可能压根没跑。结果录出来的火焰图里全是空闲,根本看不到问题。
- 真机调试:用 Chrome 远程调试 Android 设备,或 Safari Web Inspector 连接 iOS,手动快速拖拽列表
- 模拟触摸:在 Desktop Chrome DevTools 中打开
More Tools → Sensors,把Touch simulation打开,并勾选Emulate touch events - 确保滚动距离足够长:至少持续 1–2 秒,让 iScroll 的
_move和_translate方法被反复调用,否则性能毛刺太短,会被过滤掉
火焰图里重点盯住 Main 线程里的黄色/红色长条
滚动过程中的主线程任务如果超过 50ms,Chrome 会标成黄色;超过 100ms 就变红——这就是“长任务”(Long Task),是滚动卡顿的直接证据。不要只看 FPS 图表,它只是结果;要钻进 Main 轨道找源头。
- 横向拉选一段红色长条,看 Summary 面板里
Task类型是不是Script Evaluation—— 如果是,说明 JS 在干重活 - 点开 Bottom-Up 标签页,按
Self Time排序,排第一的函数大概率就是罪魁祸首,比如_move、updateScrollPosition或你自己的renderItem - 注意调用栈里是否出现
offsetHeight、getBoundingClientRect()这类强制同步布局的 API —— 它们在循环里每调一次都触发一次 Layout,iScroll 的_translate本身不慢,但如果你在scroll回调里又读 DOM 尺寸,就雪上加霜
iScroll 场景下特别容易被忽略的两个陷阱
iScroll 不是黑盒,它的性能敏感点非常明确,但很多人在优化时绕开了真正该改的地方。
-
probeType: 3开启后,iScroll 会在每次 touchmove 中调用_move,若你在on('scroll')里做了复杂计算(比如实时 filter 列表、重绘 canvas),就会立刻拖垮帧率 —— 应该把这类逻辑节流到requestIdleCallback或移到scrollEnd后执行 -
useTransform: true是默认值,但如果你在 scroller 元素上写了will-change: transform,又同时设置了overflow: hidden或border-radius,会导致 GPU 层合成失败,Chrome 会悄悄 fallback 到 CPU 渲染,_translate的 CSS 变换就失去加速效果 - 检查
scrollerStyle[utils.style.transform]实际写入的值:在火焰图中标出_translate调用后是否紧跟着大量Layout或Recalculate Style—— 如果有,说明 transform 没生效,得查父容器是否触发了层叠上下文断裂
真正卡顿的代码往往藏在「看起来无关」的地方:比如一个监听 scroll 的 IntersectionObserver 回调里做了 innerHTML 插入,或者某个防抖函数误把 leading: true 写成了 trailing: true,导致滚动中完全不响应。火焰图不会告诉你「这是 bug」,但它会冷冰冰地标出哪一行 JS 吃掉了 87ms —— 接下来是读代码,不是调参数。










