<p>滚动加载核心判断条件是 window.innerHeight + window.scrollY >= document.body.scrollHeight - 100,预留100px缓冲防抖动,并需配合loading锁、节流及IntersectionObserver等优化方案。</p>

滚动加载的核心判断条件是 window.innerHeight + window.scrollY 是否接近 document.body.scrollHeight
浏览器滚动加载(infinite scroll)本质是监听用户是否“快到底部”,而不是等滚动条完全触底才触发。因为 scrollHeight 包含所有内容高度,innerHeight 是可视区高,scrollY 是当前滚动偏移,三者关系决定了“离底部还有多远”。常见错误是只对比 scrollY === scrollHeight - innerHeight,这在移动端或动态渲染内容时极易失效——DOM 重排、字体加载延迟、图片占位未撑开都会让计算失准。
实操建议:
立即学习“前端免费学习笔记(深入)”;
- 用
scrollY + innerHeight >= scrollHeight - 100作为触发阈值(预留 100px 缓冲),避免临界抖动 - 加节流:用
setTimeout或requestIdleCallback包裹加载逻辑,防止高频触发 - 加载中状态必须锁定:设置一个
loading标志位,fetch开始前置为true,成功/失败后才置为false,否则快速滚动可能并发多次请求
IntersectionObserver 比 scroll 事件更可靠,尤其在 iOS Safari 和 WebKit 内核中
原生 scroll 事件在 iOS 上有性能问题,且无法感知元素是否真正进入视口(比如被 overflow: hidden 父容器裁剪)。IntersectionObserver 是声明式方案,由浏览器原生调度,不阻塞主线程,兼容性已覆盖 iOS 12.2+、Chrome 51+、Firefox 55+。
实操建议:
立即学习“前端免费学习笔记(深入)”;
- 监听页尾的占位
<div id="loader-trigger"></div>元素,而非监听整个窗口 - 初始化时传入
{ rootMargin: '0px 0px 200px 0px' },提前 200px 触发加载(比scroll更平滑) - 观察器回调中调用一次后立即
unobserve,避免重复触发;新内容插入后重新observe新的占位节点
HTML 模板里要预留加载状态与错误重试机制,不能只写“加载中…”
响应式模板不是静态结构,它得应对网络波动、接口限流、服务端分页错位等真实场景。纯展示“Loading…”会让人误以为卡死;没有重试按钮,用户只能刷新整页,丢失当前滚动位置和筛选状态。
实操建议:
立即学习“前端免费学习笔记(深入)”;
- 用
data-status="idle | loading | error | loaded"控制占位区样式,CSS 可针对不同状态做渐隐/骨架屏/按钮显隐 - 错误态必须带
<button onclick="loadMore()">重试</button>,且按钮内应包含当前页码或游标(如data-cursor="abc123") - 服务端分页建议用游标(cursor-based)而非页码(offset-based),避免因新数据插入导致重复或漏项
移动端需额外处理 touchmove 阻塞与 overscroll 回弹干扰
iOS 和部分安卓 WebView 在滚动到底部时会触发 overscroll 回弹,此时 scrollY 可能短暂变为负数或超大值,导致误判;同时,touchmove 默认可被阻止,若监听不当会破坏原生滚动惯性。
实操建议:
立即学习“前端免费学习笔记(深入)”;
- 在
scroll监听器开头加守卫:if (scrollY ,过滤 overscroll 噪声 - 不要对
body或html设置touch-action: none,否则禁用原生滚动惯性 - 若容器是局部滚动(如
overflow-y: auto的 div),监听目标必须是该容器,而非window,且要用container.scrollTop替代scrollY
实际最难的部分不是加载逻辑本身,而是如何让“下一页”插入后不引起页面跳动、不打断用户阅读节奏、不重复渲染已存在的 DOM 节点——这要求你严格管理插入位置、使用 documentFragment 批量挂载、并确保新内容的 CSS 高度可预测(比如图片必须设宽高比或使用 aspect-ratio)。



















