滚动卡顿主因是scroll中触发强制同步布局,应避免直接操作DOM、用transform替代top/margin、节流读取布局、加passive: true、用translateZ(0)或will-change提升图层、长列表必用虚拟滚动。
绝大多数 HTML5 页面滚动不流畅,根源在于监听 scroll 事件时直接操作 DOM 或读写 offsetTop、getBoundingClientRect() 等会强制浏览器同步计算布局。这种“强制同步布局”在每帧都发生,极易导致掉帧。
scroll 回调里调用 element.style.top = ... 或修改 class 触发位置/尺寸变化transform: translateY() 替代 top/margin-top —— 它走合成层(compositor),不触发重排el.getBoundingClientRect())尽量节流或移到 requestIdleCallback 中移动端浏览器默认把 scroll 事件标记为“可能调用 preventDefault()”,因此会等待 JS 执行完才滚动,造成明显延迟。显式声明 passive: true 可解除该阻塞。
window.addEventListener('scroll', handleScroll, {
passive: true // 关键:告诉浏览器你不会调用 preventDefault()
});
touchstart + touchmove 并在必要时设 passive: false
passive: true 时,Chrome 控制台会警告 “Unable to preventDefau
lt inside passive event listener”即使用了 transform,如果元素没被提升为独立图层,仍可能和父容器共用渲染层,导致滚动时频繁重绘。需主动触发图层分离。
transform: translateZ(0) 或 will-change: transform
will-change:只在滚动开始前设置,滚动结束 100ms 后移除,避免内存浪费 或长列表父容器设 will-change,它会导致大量纹理内存占用
当列表项超 200 行,哪怕所有优化都做了,DOM 节点过多仍会拖慢滚动。此时必须放弃渲染全部节点。
height 精确控制)scroll 时仅更新 scrollTop 和当前渲染起始索引,不新增/删除 DOMreact-window(React)、vue-virtual-scroller(Vue),或手写基于 IntersectionObserver 的简易版display: none 或 visibility: hidden 隐藏行——它们仍参与布局计算