应优先使用 intersectionobserver 替代 window.onscroll 实现滚动加载,因其天然防抖、性能更优且兼容主流浏览器;需合理设置 rootmargin 预加载、正确处理加载中状态的语义与可访问性,并配合 scrolltop 兜底检测和 isloading 状态控制防止漏载与重复请求。

为什么直接用 window.onscroll 容易卡顿或重复加载
滚动事件触发频率极高,没做节流时,onscroll 可能在 1 秒内触发上百次,导致多次并发请求、DOM 频繁重绘,甚至页面卡死。更糟的是,若判断逻辑写在滚动回调里(比如“离底部小于 100px 就加载”),极易因 DOM 渲染延迟或盒模型计算误差,连续触发两次加载。
实操建议:
- 用
IntersectionObserver替代onscroll监听占位元素是否进入视口,天然防抖、性能友好、兼容性已覆盖 Chrome 58+/Firefox 55+/Safari 12.1+ - 在首次加载后,动态插入一个
<div id="loading-trigger"></div>到列表末尾,只观察它,不监听整个窗口 - 触发加载后立即
unobserve()当前触发器,等新数据插入后再重新observe()新的占位节点,避免重复触发
IntersectionObserver 初始化时必须关掉 rootMargin 吗
不是必须关,但默认值 "0px" 往往不够用——用户还没看到底部,你就想开始加载,体验会“慢半拍”。合理设置 rootMargin 才能提前预加载。
实操建议:
- 设成
"0px 0px 200px 0px":底部预留 200px 触发,给网络和渲染留缓冲时间 - 不要设过大(如
"0px 0px 1000px 0px"),否则可能在用户还没滑到附近就发起请求,浪费带宽且干扰真实滚动节奏 - 如果容器不是
window(比如列表在固定高度的div内滚动),必须显式传入root参数,否则观察器默认以视口为根,行为不可控
加载中状态怎么插进 DOM 才不破坏语义和可访问性
很多实现直接在列表末尾追加 <div>加载中...</div>,看似简单,但会干扰屏幕阅读器焦点流,也容易被 CSS display: none 或动画隐藏后仍被读出。
实操建议:
- 用
<div aria-busy="true" aria-live="polite">正在加载更多内容</div>,并确保该节点有明确角色(如role="status") - 加载完成立刻移除该节点,而不是
innerHTML = ""或style.display = "none",否则辅助技术可能无法感知状态变更 - 若用骨架屏,每个 item 的占位结构需与真实 item 保持相同语义层级(比如都用
<article></article>包裹),避免渲染后 DOM 结构突变引发焦点丢失
如何防止用户快速滚动时漏掉某一页数据
当用户猛拖滚动条或惯性滑动时,IntersectionObserver 可能跳过触发,尤其在低端设备或页面重绘压力大时。这不是 bug,是设计使然——它只报告“进入/离开”,不保证中间帧。
实操建议:
- 在
observer回调里,检查当前滚动容器的scrollTop + clientHeight >= scrollHeight - 100(100 是安全阈值),作为兜底条件 - 维护一个
isLoading状态标志,任何加载发起前先判断,避免并发请求;成功后重置,失败后延时 1 秒再允许下一次尝试 - 服务端接口必须支持基于游标(cursor)或时间戳分页,而非传统
page=2,否则快速滚动后再次触发时,可能重复返回同一批数据
最麻烦的其实是滚动容器嵌套、CSS transform 干扰 offset 计算、以及 iOS Safari 中 IntersectionObserver 在 overscroll 时的行为差异——这些没法靠一套代码通吃,得在真机上逐个验证。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











