直接监听 scroll 事件卡顿的根源是触发过于频繁且主线程负载过重;应改用 IntersectionObserver 并配合 rootMargin 预加载、loading 锁、DOM 复用及稳定 key 等优化策略。

为什么直接监听 scroll 事件会卡顿
无限滚动卡顿的根源往往不是数据量大,而是 scroll 事件触发太频繁,且在主线程里做了太多事——比如反复调用 getBoundingClientRect()、批量 DOM 插入、或没节流就更新 React state。浏览器每秒可能触发上百次 scroll,而每次都执行重排/重绘,帧率立刻掉到 20fps 以下。
实操建议:
立即学习“前端免费学习笔记(深入)”;
- 用
IntersectionObserver替代scroll监听:它由浏览器原生调度,不抢占主线程,且能精准感知元素是否进入视口 - 避免在 observer 回调里做同步 DOM 操作;若需插入节点,用
DocumentFragment批量挂载 - 不要对每个列表项都新建 observer 实例——复用一个 observer,观察多个
target元素即可
如何用 IntersectionObserver 判断“该加载下一页”
IntersectionObserver 的核心是靠 isIntersecting 和 intersectionRatio 判断可见性,但“底部占位符是否进入视口”只是最简方案。真实场景中,需要预留缓冲距离(比如提前 200px 加载),否则用户会看到空白再跳一下。
实操建议:
立即学习“前端免费学习笔记(深入)”;
- 创建 observer 时传入
{ rootMargin: '200px 0px 0px 0px' },让目标元素上边缘距离视口底部 200px 就触发回调 - 只在
isIntersecting === true且intersectionRatio > 0时触发加载,避免重复触发 - 加载中状态要锁定:设置
loading = true,加载完成再置为false,防止快速滚动导致多次并发请求
DOM 节点复用比“删旧增新”更省性能
很多实现一滚动就 innerHTML = '' 再拼接新 HTML,这会强制浏览器销毁所有子节点、重建样式树、重排布局——尤其当列表项含图片、字体图标或 CSS 动画时,开销极大。
实操建议:
立即学习“前端免费学习笔记(深入)”;
- 用固定高度容器 +
transform: translateY()做虚拟滚动:只渲染视口内及上下各 2–3 行的节点,其余用空白占位 - 若不用虚拟滚动,至少复用 DOM 节点:维护一个
nodePool数组,把移出视口的li元素暂存,下次直接textContent和dataset更新内容 - 给列表项加
will-change: transform(仅当使用 transform 滚动时),提示浏览器启用图层加速
React/Vue 中容易忽略的 ref 和 key 问题
框架里写无限滚动,常因 ref 绑定错对象或 key 不稳定导致重渲染异常。比如用 useRef 指向滚动容器却忘了加 current,或用索引当 key 导致新增项时整个列表重排。
实操建议:
立即学习“前端免费学习笔记(深入)”;
- React 中 observer target 必须是真实 DOM 节点:用
useRef+useEffect在ref.current存在后才初始化 observer -
key必须唯一且稳定:优先用数据 ID,不用数组 index;若无 ID,可用item.id || `${item.title}-${index}`但需确保 title 不变 - 加载更多时,避免直接
setState([...prev, ...newItems])——大数据量下会引发整列表 diff;改用immer或手动 patch 更新



















