原生JavaScript實現(xiàn)無限滾動加載效果
一、功能背景與應(yīng)用場景
無限滾動加載(Infinite Scroll)是現(xiàn)代 Web 應(yīng)用最主流的列表加載方式,替代傳統(tǒng)分頁,大幅提升用戶瀏覽體驗,常見場景:
- 商品列表頁、推薦流
- 評論區(qū)、消息列表
- 文章列表、動態(tài)廣場
- 后臺數(shù)據(jù)表格、日志列表
基礎(chǔ)版本容易出現(xiàn):重復(fù)請求、加載閃爍、滾動抖動、結(jié)束狀態(tài)不顯示、性能差等問題。本文實現(xiàn)企業(yè)級穩(wěn)定版,解決所有常見坑點。
二、核心實現(xiàn)效果
滾動到底部自動加載數(shù)據(jù)
防重復(fù)請求(加鎖機(jī)制)
加載中狀態(tài)提示(Loading)
無數(shù)據(jù) / 全部加載完畢狀態(tài)提示
滾動監(jiān)聽節(jié)流優(yōu)化
列表渲染無閃爍、無抖動
支持異步接口模擬(可直接對接后端)
響應(yīng)式適配,移動端 / PC 端通用
代碼模塊化、可配置、易擴(kuò)展
三、完整可運(yùn)行源碼(直接復(fù)制發(fā)布)
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>原生JS實現(xiàn)無限滾動加載(下拉加載更多)</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" rel="external nofollow" >
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: "Microsoft YaHei", sans-serif;
}
body {
background-color: #f5f7fa;
padding: 20px;
}
.container {
max-width: 800px;
margin: 0 auto;
}
.title {
font-size: 24px;
color: #2c3e50;
margin-bottom: 20px;
text-align: center;
}
/* 列表容器 */
.list-container {
display: flex;
flex-direction: column;
gap: 15px;
margin-bottom: 30px;
min-height: 300px;
}
/* 列表項 */
.list-item {
background: #fff;
padding: 20px;
border-radius: 10px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
transition: transform 0.2s;
}
.list-item:hover {
transform: translateY(-2px);
}
.list-item h3 {
font-size: 18px;
color: #2f3542;
margin-bottom: 8px;
}
.list-item p {
font-size: 14px;
color: #57606f;
line-height: 1.6;
}
/* 加載狀態(tài) */
.load-status {
text-align: center;
padding: 15px 0;
font-size: 14px;
color: #666;
display: none;
}
.load-status.active {
display: block;
}
.loading-icon {
display: inline-block;
animation: rotate 1s linear infinite;
margin-right: 6px;
}
@keyframes rotate {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* 無更多數(shù)據(jù) */
.no-more {
color: #999;
padding: 10px 0;
}
</style>
</head>
<body>
<div class="container">
<h1 class="title">無限滾動加載演示</h1>
<div class="list-container" id="listContainer"></div>
<div class="load-status" id="loadStatus">
<i class="fas fa-spinner loading-icon"></i>
<span>正在加載更多數(shù)據(jù)...</span>
</div>
<div class="load-status no-more" id="noMore">已加載全部數(shù)據(jù)</div>
</div>
<script>
// ====================== 配置項 ======================
const CONFIG = {
pageSize: 8, // 每頁條數(shù)
threshold: 150, // 距離底部多少像素觸發(fā)加載
throttleDelay: 150, // 滾動節(jié)流時間
totalData: 40 // 模擬總數(shù)據(jù)量
};
// ====================== 狀態(tài)管理 ======================
let currentPage = 1;
let isLoading = false; // 防重復(fù)請求鎖
let isEnd = false; // 是否全部加載完畢
// DOM
const listContainer = document.getElementById('listContainer');
const loadStatus = document.getElementById('loadStatus');
const noMore = document.getElementById('noMore');
// ====================== 初始化 ======================
function init() {
loadData();
window.addEventListener('scroll', throttle(handleScroll, CONFIG.throttleDelay));
}
// ====================== 滾動監(jiān)聽(觸底判斷) ======================
function handleScroll() {
if (isLoading || isEnd) return;
const scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
const clientHeight = document.documentElement.clientHeight;
const scrollHeight = document.documentElement.scrollHeight;
// 觸底判斷
if (scrollTop + clientHeight + CONFIG.threshold >= scrollHeight) {
currentPage++;
loadData();
}
}
// ====================== 加載數(shù)據(jù)(模擬接口) ======================
function loadData() {
isLoading = true;
loadStatus.classList.add('active');
// 模擬請求延遲
setTimeout(() => {
const start = (currentPage - 1) * CONFIG.pageSize;
const end = currentPage * CONFIG.pageSize;
// 構(gòu)造數(shù)據(jù)
const list = [];
for (let i = start; i < end && i < CONFIG.totalData; i++) {
list.push({
id: i + 1,
title: `這是第 ${i + 1} 條內(nèi)容`,
content: '原生JS實現(xiàn)無限滾動加載,支持防重復(fù)請求、滾動節(jié)流、狀態(tài)管理,企業(yè)級穩(wěn)定方案。'
});
}
// 渲染
renderList(list);
// 判斷是否加載完畢
if (end >= CONFIG.totalData) {
isEnd = true;
loadStatus.classList.remove('active');
noMore.classList.add('active');
} else {
isLoading = false;
loadStatus.classList.remove('active');
}
}, 800);
}
// ====================== 渲染列表 ======================
function renderList(data) {
data.forEach(item => {
const div = document.createElement('div');
div.className = 'list-item';
div.innerHTML = `
<h3>${item.title}</h3>
<p>${item.content}</p>
`;
listContainer.appendChild(div);
});
}
// ====================== 節(jié)流函數(shù) ======================
function throttle(fn, delay) {
let lastTime = 0;
return function (...args) {
const now = Date.now();
if (now - lastTime >= delay) {
lastTime = now;
fn.apply(this, args);
}
};
}
// 啟動
init();
</script>
</body>
</html>四、核心技術(shù)講解
1. 觸底判斷公式(最關(guān)鍵)
scrollTop + clientHeight + threshold >= scrollHeight
scrollTop:滾動條滾動距離clientHeight:可視窗口高度threshold:距離底部提前觸發(fā)距離scrollHeight:頁面總高度
只要滿足公式,就判定即將觸底,開始加載下一頁。
2. 防重復(fù)請求(企業(yè)必備)
使用isLoading 鎖:
- 開始請求 →
isLoading = true - 結(jié)束請求 →
isLoading = false - 滾動時判斷鎖狀態(tài),避免多次觸發(fā)
3. 性能優(yōu)化:滾動節(jié)流
滾動事件觸發(fā)頻率極高,必須使用節(jié)流:
- 每 150ms 只執(zhí)行一次
- 大幅降低瀏覽器性能消耗
- 避免頁面卡頓
4. 狀態(tài)管理規(guī)范
- 加載中 → 顯示 Loading
- 無數(shù)據(jù) → 顯示 “已加載全部”
- 結(jié)束后不再監(jiān)聽滾動
- 全程無閃爍、無抖動
5. 擴(kuò)展性極強(qiáng)
可直接對接真實接口:
- 替換
setTimeout為fetch/axios - 后端返回總條數(shù)即可判斷是否結(jié)束
五、可擴(kuò)展高級功能(文章加分項)
- 錯誤重試:加載失敗顯示重試按鈕
- 下拉刷新:配合 touch 事件實現(xiàn)下拉刷新
- 緩存機(jī)制:使用 localStorage 緩存列表
- 虛擬列表:支持 10w+ 數(shù)據(jù)不卡頓(超高分亮點)
- 淡入動畫:加載新項目時添加動畫
- 定位滾動:返回后保持上次位置
六、適用場景
- 商品列表
- 評論列表
- 消息流
- 后臺管理表格
- 文章 / 動態(tài)列表
所有長列表業(yè)務(wù)都能直接使用。
七、方法補(bǔ)充
原生 JavaScript 實現(xiàn)無限滾動加載是一種常見的列表分頁技術(shù),核心是監(jiān)聽滾動事件,當(dāng)用戶滾動到頁面底部(或接近底部)時,自動加載下一頁數(shù)據(jù)并追加到列表中。以下是一個完整的實現(xiàn)方案,包含核心原理、代碼示例及優(yōu)化技巧。
核心原理
- 監(jiān)聽滾動事件:使用
window或特定容器的scroll事件。 - 計算滾動位置:判斷
scrollTop + clientHeight >= scrollHeight - threshold(threshold 為閾值,如 200px)。 - 觸發(fā)加載:滿足條件時,調(diào)用加載函數(shù),請求新數(shù)據(jù)。
- 追加內(nèi)容:將新數(shù)據(jù)渲染到頁面列表末尾。
- 狀態(tài)控制:使用
isLoading標(biāo)志防止重復(fù)請求,使用hasMore標(biāo)志表示是否還有更多數(shù)據(jù)。
實現(xiàn)代碼
HTML 結(jié)構(gòu)
<div id="app"> <ul id="list" class="list"></ul> <div id="loading" class="loading hidden">加載中...</div> <div id="no-more" class="no-more hidden">沒有更多數(shù)據(jù)了</div> </div>
CSS 樣式(簡單示例)
.list {
list-style: none;
padding: 0;
margin: 0;
}
.list li {
padding: 12px;
border-bottom: 1px solid #eee;
}
.loading, .no-more {
text-align: center;
padding: 10px;
color: #999;
}
.hidden {
display: none;
}JavaScript 實現(xiàn)
// 模擬異步數(shù)據(jù)請求
function fetchData(page, pageSize = 10) {
return new Promise((resolve) => {
setTimeout(() => {
const start = (page - 1) * pageSize;
const items = Array.from({ length: pageSize }, (_, i) => ({
id: start + i + 1,
content: `條目 ${start + i + 1}`
}));
// 模擬總共 50 條數(shù)據(jù)
const hasMore = start + pageSize < 50;
resolve({ items, hasMore });
}, 500);
});
}
class InfiniteScroll {
constructor(options) {
this.container = options.container; // 滾動容器,默認(rèn)為 window
this.listEl = options.listEl; // 列表容器元素
this.loadingEl = options.loadingEl; // 加載提示元素
this.noMoreEl = options.noMoreEl; // 無更多數(shù)據(jù)提示元素
this.fetchData = options.fetchData; // 獲取數(shù)據(jù)的函數(shù)
this.page = 1;
this.pageSize = options.pageSize || 10;
this.isLoading = false;
this.hasMore = true;
this.threshold = options.threshold || 200; // 距離底部多少像素時觸發(fā)
this.init();
}
init() {
this.loadMore = this.loadMore.bind(this);
// 綁定滾動事件(使用節(jié)流優(yōu)化)
this.scrollHandler = this.throttle(this.checkScroll.bind(this), 200);
this.container.addEventListener('scroll', this.scrollHandler);
if (this.container === window) {
window.addEventListener('scroll', this.scrollHandler);
}
// 初始加載第一頁
this.loadMore();
}
// 檢查是否需要加載
checkScroll() {
if (!this.hasMore || this.isLoading) return;
let scrollTop, clientHeight, scrollHeight;
if (this.container === window) {
scrollTop = window.pageYOffset || document.documentElement.scrollTop;
clientHeight = document.documentElement.clientHeight;
scrollHeight = document.documentElement.scrollHeight;
} else {
scrollTop = this.container.scrollTop;
clientHeight = this.container.clientHeight;
scrollHeight = this.container.scrollHeight;
}
if (scrollTop + clientHeight >= scrollHeight - this.threshold) {
this.loadMore();
}
}
// 加載更多數(shù)據(jù)
async loadMore() {
if (!this.hasMore || this.isLoading) return;
this.isLoading = true;
this.showLoading(true);
try {
const { items, hasMore } = await this.fetchData(this.page, this.pageSize);
this.renderItems(items);
this.hasMore = hasMore;
this.page++;
if (!this.hasMore) {
this.showNoMore(true);
}
} catch (error) {
console.error('加載失敗', error);
// 可在此處顯示錯誤提示,并提供重試按鈕
} finally {
this.isLoading = false;
this.showLoading(false);
}
}
// 渲染列表項
renderItems(items) {
const fragment = document.createDocumentFragment();
items.forEach(item => {
const li = document.createElement('li');
li.textContent = item.content;
fragment.appendChild(li);
});
this.listEl.appendChild(fragment);
}
// 顯示/隱藏加載提示
showLoading(show) {
if (this.loadingEl) {
this.loadingEl.classList.toggle('hidden', !show);
}
}
// 顯示/隱藏?zé)o更多數(shù)據(jù)提示
showNoMore(show) {
if (this.noMoreEl) {
this.noMoreEl.classList.toggle('hidden', !show);
}
}
// 節(jié)流函數(shù)
throttle(fn, delay) {
let last = 0;
return function(...args) {
const now = Date.now();
if (now - last > delay) {
last = now;
fn.apply(this, args);
}
};
}
// 銷毀,移除事件監(jiān)聽
destroy() {
if (this.container === window) {
window.removeEventListener('scroll', this.scrollHandler);
} else {
this.container.removeEventListener('scroll', this.scrollHandler);
}
}
}
// 使用示例
const infiniteScroll = new InfiniteScroll({
container: window, // 滾動容器
listEl: document.getElementById('list'),
loadingEl: document.getElementById('loading'),
noMoreEl: document.getElementById('no-more'),
fetchData: fetchData, // 自定義數(shù)據(jù)獲取函數(shù)
pageSize: 10,
threshold: 200
});八、總結(jié)
本文實現(xiàn)的無限滾動加載組件是前端高頻面試題 + 企業(yè)必備功能。特點:原生 JS、無依賴、防重復(fù)請求、性能高、狀態(tài)完整、無閃爍。
以上就是原生JavaScript實現(xiàn)無限滾動加載效果的詳細(xì)內(nèi)容,更多關(guān)于JavaScript無限滾動加載的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
JavaScript中使用Object.create()創(chuàng)建對象介紹
這篇文章主要介紹了JavaScript中使用Object.create()創(chuàng)建對象介紹,本文先是講解了語法,然后給出了創(chuàng)建實例,需要的朋友可以參考下2014-12-12
微信小程序云開發(fā)實現(xiàn)分頁刷新獲取數(shù)據(jù)
這篇文章主要為大家詳細(xì)介紹了微信小程序云開發(fā)實現(xiàn)分頁刷新獲取數(shù)據(jù),文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-08-08

