IntersectionObserver可高效实现懒加载,它不依赖scroll事件和位置计算,通过监听元素可见性变化天然防抖、不丢帧;配置rootMargin提前触发、threshold控制阈值,并配合sentinel占位节点与状态锁,避免卡顿、漏触发及并发请求。

用 IntersectionObserver 监听滚动触发动态加载
直接监听 window.onscroll 做懒加载,99% 的情况会卡顿或漏触发——因为图片异步加载、广告插入、动态高度变化都会让 scrollTop + innerHeight >= scrollHeight 判断失效。现代方案应绕过 scroll 事件,改用浏览器原生的 IntersectionObserver。
它不依赖滚动位置计算,只关注“元素是否可见”,天然防抖、不丢帧、DOM 高度变化不影响判断。
- 在列表末尾插入一个占位
- 创建 observer 实例时配置
{ rootMargin: "0px 0px 200px 0px", threshold: 0 },提前 200px 触发,避免用户快速滚动时白屏 - 回调中检查
entries[0].isIntersecting为true再调用loadMore() - 加载完成后,重新绑定新占位节点(或复用原节点),并确保有
isLoading状态锁防止并发请求
fetch + insertAdjacentHTML 追加 HTML 片段
后端返回的不是纯数据而是完整 HTML 片段(如 摘要)时,不要先解析再拼字符串,直接插入最高效。标题
- 用
fetch("/api/articles?page=2")请求,响应类型设为text(不是json),避免 JSON → HTML 转换开销 - 拿到响应体后,调用
document.getElementById("article-list").insertAdjacentHTML("beforeend", htmlText) - 避免用
innerHTML += htmlText,会强制重绘整个容器,长列表下性能断崖式下降 - 插入前可加一层简单校验:比如检查返回内容是否包含
开头,防止服务端错误返回空或报错 HTML
为什么不能只靠 loading="lazy" 加载整块内容
loading="lazy" 是浏览器级优化,但它只对 和 生效,且仅控制资源请求时机,不适用于「整块文章区块」的按需渲染。
- 它无法触发 JavaScript 逻辑(比如更新页码、禁用按钮、上报埋点)
- 不支持自定义加载阈值或预加载行为(如提前 300px 加载)
- 对
、或组件级内容完全无效 - 若后端未返回真实 HTML,而是需要前端模板编译,那
loading="lazy"根本无从介入
容易被忽略的边界问题
真正上线后出问题的,往往不是主流程,而是这些细节:
- 占位节点被重复 observe:每次加载完必须调用
observer.unobserve(sentinel),再observer.observe(newSentinel),否则可能多次触发 - 页面初始高度不足一屏,导致 sentinel 一开始就处于相交状态,需在初始化时加
if (sentinel.getBoundingClientRect().top 防止首屏漏加载 - 移动端 WebView(尤其旧版 Android)可能不支持
rootMargin,建议 fallback 到{ threshold: 0.1 }并搭配getBoundingClientRect()简单兜底 - 服务端分页接口没做幂等,用户手抖连点两次“加载更多”,可能重复插入相同文章——应在前端加防抖,或后端用游标(cursor)替代页码
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











