滚动卡顿主因是scroll中触发强制同步布局,应避免直接操作DOM、用transform替代top/margin、节流读取布局、加passive: true、用translateZ(0)或will-change提升图层、长列表必用虚拟滚动。

滚动卡顿是因为触发了重排(reflow)
绝大多数 HTML5 页面滚动不流畅,根源在于监听 scroll 事件时直接操作 DOM 或读写 offsetTop、getBoundingClientRect() 等会强制浏览器同步计算布局。这种“强制同步布局”在每帧都发生,极易导致掉帧。
- 避免在
scroll回调里调用element.style.top = ...或修改 class 触发位置/尺寸变化 - 用
transform: translateY()替代top/margin-top—— 它走合成层(compositor),不触发重排 - 读取布局信息(如
el.getBoundingClientRect())尽量节流或移到requestIdleCallback中
使用 passive: true 防止 scroll 事件阻塞主线程
移动端浏览器默认把 scroll 事件标记为“可能调用 preventDefault()”,因此会等待 JS 执行完才滚动,造成明显延迟。显式声明 passive: true 可解除该阻塞。
window.addEventListener('scroll', handleScroll, {
passive: true // 关键:告诉浏览器你不会调用 preventDefault()
});
- 若确实需要阻止滚动(如下拉刷新),改用
touchstart+touchmove并在必要时设passive: false - 不加
passive: true时,Chrome 控制台会警告 “Unable to preventDefault inside passive event listener” - 兼容性没问题:Chrome 51+、Firefox 53+、Safari 11.1+、Edge 79+ 均支持
CSS 层级和 will-change 让滚动走 GPU 合成
即使用了 transform,如果元素没被提升为独立图层,仍可能和父容器共用渲染层,导致滚动时频繁重绘。需主动触发图层分离。
- 对滚动中要动画的元素加
transform: translateZ(0)或will-change: transform - 慎用
will-change:只在滚动开始前设置,滚动结束 100ms 后移除,避免内存浪费 - 避免给整个
或长列表父容器设will-change,它会导致大量纹理内存占用 - 用 Chrome DevTools → Rendering → “Paint flashing” 和 “Layer borders” 查看是否成功分层
虚拟滚动(virtual scrolling)是长列表唯一靠谱解法
当列表项超 200 行,哪怕所有优化都做了,DOM 节点过多仍会拖慢滚动。此时必须放弃渲染全部节点。
立即学习“前端免费学习笔记(深入)”;
- 核心逻辑:只渲染视口内 + 上下各缓冲 1~2 屏的节点,其余用空白占位(
height精确控制) - 监听
scroll时仅更新scrollTop和当前渲染起始索引,不新增/删除 DOM - 推荐轻量方案:
react-window(React)、vue-virtual-scroller(Vue),或手写基于IntersectionObserver的简易版 - 别用
display: none或visibility: hidden隐藏行——它们仍参与布局计算











