在内容型网站或者列表类页面中,滚动到底部自动加载更多是非常常见的交互设计,能够提升用户浏览体验,减少手动操作。实现这个功能主要依赖JavaScript的滚动监听和DOM尺寸计算能力,下面我们一步步拆解实现逻辑。

实现原理
要实现滚动到底部触发加载,首先需要明确三个核心数值:
- scrollTop:当前页面滚动的距离,也就是视口顶部到文档顶部的距离
- clientHeight:当前视口的高度,也就是浏览器可见区域的高度
- scrollHeight:整个文档的总高度,包括超出视口不可见的部分
当用户滚动到页面底部时,会满足scrollTop + clientHeight >= scrollHeight - 阈值的条件,这里的阈值可以设置一个较小的值,比如50px,避免用户必须完全滚到最底部才触发,提升体验。
基础实现步骤
1. 准备基础HTML结构
先搭建一个简单的列表容器和加载状态提示的结构:
<div id="list-container"> <!-- 列表内容会动态插入到这里 --> </div> <div id="loading-tip" style="text-align: center; padding: 10px; display: none;"> 加载中... </div> <div id="no-more-tip" style="text-align: center; padding: 10px; display: none;"> 没有更多内容了 </div>
2. 编写滚动监听逻辑
监听window的scroll事件,在事件回调中计算三个核心数值,判断是否满足加载条件:
// 是否正在加载中,避免重复请求
let isLoading = false;
// 是否还有更多数据
let hasMore = true;
// 当前页码
let page = 1;
// 每页加载条数
const pageSize = 10;
// 获取DOM元素
const listContainer = document.getElementById('list-container');
const loadingTip = document.getElementById('loading-tip');
const noMoreTip = document.getElementById('no-more-tip');
// 滚动事件处理函数
function handleScroll() {
// 如果正在加载或者没有更多数据,直接返回
if (isLoading || !hasMore) return;
// 获取当前滚动距离
const scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
// 获取视口高度
const clientHeight = document.documentElement.clientHeight || document.body.clientHeight;
// 获取文档总高度
const scrollHeight = document.documentElement.scrollHeight || document.body.scrollHeight;
// 设置触发阈值,距离底部50px就触发加载
const threshold = 50;
if (scrollTop + clientHeight >= scrollHeight - threshold) {
// 触发加载逻辑
loadMoreData();
}
}
// 监听滚动事件
window.addEventListener('scroll', handleScroll);
3. 实现数据加载和追加逻辑
加载数据时需要先修改加载状态,请求完成后更新列表和状态:
// 模拟异步请求数据,实际项目中替换为真实接口
function fetchData(page, pageSize) {
return new Promise((resolve) => {
// 模拟接口延迟
setTimeout(() => {
// 模拟返回数据,最多返回3页,之后返回空数组表示没有更多数据
if (page > 3) {
resolve([]);
} else {
const data = [];
for (let i = 0; i < pageSize; i++) {
data.push(`列表项 ${(page - 1) * pageSize + i + 1}`);
}
resolve(data);
}
}, 1000);
});
}
// 加载更多数据
async function loadMoreData() {
// 设置加载状态
isLoading = true;
loadingTip.style.display = 'block';
try {
// 请求数据
const newData = await fetchData(page, pageSize);
if (newData.length > 0) {
// 有新数据,追加到列表
newData.forEach(item => {
const div = document.createElement('div');
div.className = 'list-item';
div.style.padding = '15px';
div.style.borderBottom = '1px solid #eee';
div.textContent = item;
listContainer.appendChild(div);
});
// 页码加1
page++;
} else {
// 没有更多数据
hasMore = false;
noMoreTip.style.display = 'block';
}
} catch (error) {
console.error('加载数据失败:', error);
} finally {
// 重置加载状态
isLoading = false;
loadingTip.style.display = 'none';
}
}
// 初始加载第一页数据
loadMoreData();
优化注意事项
上面的基础实现可以满足简单场景的需求,但在实际项目中还需要做一些优化:
- 滚动事件防抖:scroll事件触发频率非常高,直接绑定回调会造成性能浪费,可以用防抖函数限制回调执行频率,比如每隔200ms执行一次判断
- 接口请求取消:如果用户快速滚动,可能会出现多个请求同时发出的情况,可以结合AbortController取消未完成的请求,避免数据错乱
- SSR场景适配:如果是服务端渲染的页面,需要注意DOM加载完成后再绑定滚动事件,避免获取不到DOM元素
- 移动端适配:移动端的滚动容器可能不是window,而是某个自定义容器,这时候需要监听对应容器的scroll事件,计算对应容器的scrollTop、clientHeight和scrollHeight
防抖优化示例
下面是给滚动事件添加防抖处理的示例代码:
// 防抖函数
function debounce(fn, delay) {
let timer = null;
return function(...args) {
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, args);
timer = null;
}, delay);
};
}
// 用防抖后的函数替换原来的滚动回调
window.addEventListener('scroll', debounce(handleScroll, 200));
JavaScript滚动监听加载更多事件处理异步请求修改时间:2026-06-09 11:30:27