无限滚动核心是「判断时机」+「防重复」+「状态同步」;推荐用IntersectionObserver监听占位元素并设rootMargin预留缓冲,避免scroll事件卡顿;加载时应追加而非清空DOM;后端须返回hasMore字段明确分页状态。

无限滚动不是靠“一直往下滚”实现的,而是靠监听用户是否快到底部、再触发加载——核心是「判断时机」+「防重复」+「状态同步」,三者缺一不可。
用 window.addEventListener('scroll') 判断是否接近底部
这是最直接的方式,但容易误触或卡顿。关键不是看“是否到底”,而是看“距离底部还有多少像素时该发起请求”:
-
scrollTop是页面已滚动高度,scrollHeight是整个文档高度,clientHeight是可视区高度 - 计算公式:
scrollHeight - scrollTop - clientHeight ,表示离底部 ≤100px 就加载 - 必须用
document.documentElement.scrollTop,而不是document.body.scrollTop,否则在 Chrome/Firefox 下可能取错值 - 别在每次
scroll里直接发请求,否则快速滚动会触发十几次,后端扛不住
为什么推荐 IntersectionObserver 而不是 scroll 事件
因为 scroll 事件在主线程执行,频繁触发会导致页面卡顿;而 IntersectionObserver 是异步、低优先级、不阻塞渲染的原生 API:
- 只需监听一个占位元素(比如
),它放在列表末尾 - 创建 observer 时设
rootMargin: '200px',意思是“目标进入视口前 200px 就触发”,留出网络和渲染缓冲 - 回调里检查
entries[0].isIntersecting === true才加载,避免误判 - 加载中要调用
observer.unobserve(sentinel),加载完再observer.observe(sentinel),否则会重复触发
innerHTML = '' 清空骨架屏是危险操作
很多人加载新内容前,先用 innerHTML = '' 把旧骨架屏清掉,再插入新 HTML —— 这会导致:
- Vue/React 组件实例被销毁,状态全丢,比如表单输入、折叠展开状态
- 第三方插件(如
Prism.js代码高亮、Chart.js图表)不会自动重初始化 - 焦点元素(
focus())丢失,键盘操作中断 - 正确做法是:用
appendChild()或insertAdjacentHTML('beforeend', ...)追加内容,保留已有 DOM 结构
后端必须返回 hasMore: true/false 字段
前端不能靠“没数据了就停”来判断终止,因为网络错误、空响应、分页越界都可能导致返回空数组,但其实还有下一页:
- 服务端接口必须明确返回字段,例如:
{ data: [...], hasMore: false } - 前端据此控制是否继续挂载
IntersectionObserver或是否允许再次触发scroll加载 - 如果用游标分页(
?cursor=xxx),注意游标失效场景(如数据被删),应 fallback 到时间戳 + ID 复合游标 - 别把
page=1,2,3...拼在 URL 里硬编码,要用变量拼接,否则翻页逻辑一改全崩
真正难的不是“怎么加载”,而是“怎么让加载不破坏已有交互、不重复请求、不掩盖失败”。很多问题出现在 DOM 替换方式、观察器生命周期管理、以及前后端分页语义没对齐上——这些地方一旦写错,调试成本远高于初次实现。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











