无限滚动是提升内容浏览体验的常用功能,核心是通过JavaScript监听页面滚动行为,在用户接近底部时自动触发数据加载和渲染,不需要手动操作翻页。下面详细介绍完整的实现步骤和注意事项。

实现核心逻辑
1. 监听滚动事件
首先需要监听窗口的scroll事件,获取当前滚动位置和页面总高度,判断是否已经滚动到接近底部的位置。
// 监听窗口滚动事件
window.addEventListener('scroll', handleScroll);
function handleScroll() {
// 获取当前滚动高度:已滚动的内容高度
const scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
// 获取可视区域高度:当前窗口可以看到的内容高度
const clientHeight = document.documentElement.clientHeight || window.innerHeight;
// 获取页面总高度:整个文档的内容高度
const scrollHeight = document.documentElement.scrollHeight || document.body.scrollHeight;
// 设置加载阈值,滚动到距离底部200px时触发加载
const threshold = 200;
// 判断是否到达加载位置
if (scrollTop + clientHeight >= scrollHeight - threshold) {
console.log('触发加载更多');
}
}2. 添加加载状态控制
为了避免滚动时多次触发请求,需要添加加载状态标记,正在请求数据时禁止再次触发请求。
// 标记是否正在加载
let isLoading = false;
// 标记是否还有更多数据
let hasMore = true;
// 当前加载的页码
let currentPage = 1;
// 每页加载的数据条数
const pageSize = 10;
function handleScroll() {
if (isLoading || !hasMore) return;
const scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
const clientHeight = document.documentElement.clientHeight || window.innerHeight;
const scrollHeight = document.documentElement.scrollHeight || document.body.scrollHeight;
const threshold = 200;
if (scrollTop + clientHeight >= scrollHeight - threshold) {
loadMoreData();
}
}3. 实现数据加载和渲染
触发加载后调用接口获取新数据,渲染到页面中,同时更新加载状态和页码。
// 获取内容容器
const contentContainer = document.getElementById('content-container');
async function loadMoreData() {
isLoading = true;
// 显示加载提示
showLoadingTip();
try {
// 模拟异步请求,实际开发中替换为真实接口地址,如http://ipipp.com/api/list
const res = await mockFetchData(currentPage, pageSize);
const dataList = res.data;
if (dataList.length === 0) {
// 没有更多数据
hasMore = false;
showNoMoreTip();
} else {
// 渲染新数据
renderData(dataList);
currentPage++;
}
} catch (error) {
console.error('加载数据失败', error);
showErrorTip();
} finally {
isLoading = false;
hideLoadingTip();
}
}
// 模拟数据请求
function mockFetchData(page, size) {
return new Promise((resolve) => {
setTimeout(() => {
const data = [];
// 模拟生成数据,最多加载5页
if (page <= 5) {
for (let i = 0; i < size; i++) {
data.push({
id: (page - 1) * size + i + 1,
title: `列表项 ${(page - 1) * size + i + 1}`,
content: `这是第${page}页的第${i + 1}条内容`
});
}
}
resolve({ data });
}, 1000);
});
}
// 渲染数据到页面
function renderData(list) {
const fragment = document.createDocumentFragment();
list.forEach(item => {
const div = document.createElement('div');
div.className = 'list-item';
div.innerHTML = `${item.title}
${item.content}
`;
fragment.appendChild(div);
});
contentContainer.appendChild(fragment);
}4. 性能优化处理
滚动事件触发频率很高,直接使用会导致性能问题,可以通过节流函数优化。
// 节流函数,控制滚动事件触发频率
function throttle(fn, delay = 200) {
let timer = null;
return function(...args) {
if (timer) return;
timer = setTimeout(() => {
fn.apply(this, args);
timer = null;
}, delay);
};
}
// 使用节流后的滚动处理函数
window.addEventListener('scroll', throttle(handleScroll, 200));完整HTML示例
下面是包含样式和结构的完整示例,可以直接运行查看效果。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>JavaScript无限滚动示例</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: Arial, sans-serif;
padding: 20px;
}
.list-item {
padding: 15px;
margin-bottom: 10px;
border: 1px solid #eee;
border-radius: 4px;
}
.list-item h3 {
margin-bottom: 8px;
color: #333;
}
.list-item p {
color: #666;
line-height: 1.5;
}
.tip {
text-align: center;
padding: 15px;
color: #999;
font-size: 14px;
}
.loading {
color: #1890ff;
}
.error {
color: #ff4d4f;
}
</style>
</head>
<body>
<h1>无限滚动示例</h1>
<div id="content-container"></div>
<div id="tip" class="tip">向下滚动加载更多</div>
<script>
const contentContainer = document.getElementById('content-container');
const tipElement = document.getElementById('tip');
let isLoading = false;
let hasMore = true;
let currentPage = 1;
const pageSize = 10;
// 节流函数
function throttle(fn, delay = 200) {
let timer = null;
return function(...args) {
if (timer) return;
timer = setTimeout(() => {
fn.apply(this, args);
timer = null;
}, delay);
};
}
// 滚动处理函数
function handleScroll() {
if (isLoading || !hasMore) return;
const scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
const clientHeight = document.documentElement.clientHeight || window.innerHeight;
const scrollHeight = document.documentElement.scrollHeight || document.body.scrollHeight;
const threshold = 200;
if (scrollTop + clientHeight >= scrollHeight - threshold) {
loadMoreData();
}
}
// 加载数据
async function loadMoreData() {
isLoading = true;
tipElement.textContent = '加载中...';
tipElement.className = 'tip loading';
try {
const res = await mockFetchData(currentPage, pageSize);
const dataList = res.data;
if (dataList.length === 0) {
hasMore = false;
tipElement.textContent = '没有更多内容了';
} else {
renderData(dataList);
currentPage++;
tipElement.textContent = '向下滚动加载更多';
}
} catch (error) {
console.error('加载失败', error);
tipElement.textContent = '加载失败,请稍后重试';
tipElement.className = 'tip error';
} finally {
isLoading = false;
}
}
// 模拟请求
function mockFetchData(page, size) {
return new Promise((resolve) => {
setTimeout(() => {
const data = [];
if (page <= 5) {
for (let i = 0; i < size; i++) {
data.push({
id: (page - 1) * size + i + 1,
title: `列表项 ${(page - 1) * size + i + 1}`,
content: `这是第${page}页的第${i + 1}条内容,用于演示无限滚动效果`
});
}
}
resolve({ data });
}, 1000);
});
}
// 渲染数据
function renderData(list) {
const fragment = document.createDocumentFragment();
list.forEach(item => {
const div = document.createElement('div');
div.className = 'list-item';
div.innerHTML = `
<h3>${item.title}</h3>
<p>${item.content}</p>
`;
fragment.appendChild(div);
});
contentContainer.appendChild(fragment);
}
// 绑定节流后的滚动事件
window.addEventListener('scroll', throttle(handleScroll, 200));
// 初始加载第一页数据
loadMoreData();
</script>
</body>
</html>注意事项
- 加载阈值可以根据页面实际布局调整,避免触发过早或过晚
- 真实项目中需要替换模拟请求为真实接口,注意处理接口鉴权、参数传递等问题
- 如果页面内容动态变化,需要重新计算
scrollHeight的值,避免判断出现偏差 - 可以在组件销毁时移除滚动事件监听,避免内存泄漏
JavaScript无限滚动事件监听异步请求DOM操作修改时间:2026-06-03 00:26:28