摘要:在本教程中,您将学习如何实现 JavaScript 无限滚动功能。
您将要构建的内容
下图展示了您将要构建的 web 应用程序

该页面将显示来自 API 的引号列表。默认情况下,它将显示 10 个引号。
如果您向下滚动到页面底部,web 应用程序将显示一个加载指示器。此外,它将调用 API 获取更多引号并将其追加到当前列表中。
您将使用的 API 的 URL 如下
https://api.javascripttutorial.net/v1/quotes/?page=1&limit=10
Code language: JavaScript (javascript)
API 接受两个查询字符串:page
和 limit
。这些查询字符串允许您从服务器对引号进行分页。
引号被划分为由 page
查询字符串确定的页面。每个页面都有由 limit
参数指定数量的引号。

单击此处查看使用 JavaScript 无限滚动功能的最终 web 应用程序。
创建项目结构
首先,创建一个名为 infinite-scroll
的新文件夹。在该文件夹内,创建两个子文件夹 css
和 js
。
其次,在 css
文件夹中创建 style.css
,在 js
文件夹中创建 app.js
。
第三,在 infinite-scroll
文件夹中创建一个新的 HTML 文件 index.html。
最终的项目文件夹结构如下所示

向 index.html 文件添加代码
打开 index.html
并向其中添加以下代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>JavaScript Infinite Scroll - Quotes</title>
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<div class="container">
<h1>Programming Quotes</h1>
<div class="quotes">
</div>
<div class="loader">
<div></div>
<div></div>
<div></div>
</div>
</div>
<script src="js/app.js"></script>
</body>
</html>
Code language: HTML, XML (xml)
在 index.html
文件中,将 style.css
放置在头部部分,将 app.js
放置在主体部分。
主体部分包含一个类名为 container
的 div
。容器元素有四个子元素
- 一个标题一(h1),显示页面标题。
- 一个类名为
quotes
的div
,它将成为所有引号的父元素。 - 一个加载器,显示加载指示器。默认情况下,加载指示器不可见。
制作 app.js
以下代码使用 querySelector()
选择类名为 quotes
的 div
和 loader
。
const quotesEl = document.querySelector('.quotes');
const loader = document.querySelector('.loader');
Code language: JavaScript (javascript)
getQuotes() 函数
以下 getQuotes()
函数调用 API 并返回引号
const getQuotes = async (page, limit) => {
const API_URL = `https://api.javascripttutorial.net/v1/quotes/?page=${page}&limit=${limit}`;
const response = await fetch(API_URL);
// handle 404
if (!response.ok) {
throw new Error(`An error occurred: ${response.status}`);
}
return await response.json();
}
Code language: JavaScript (javascript)
getQuotes()
函数接受两个参数:page
和 limit
。它使用 Fetch API 从 API 获取数据。
由于 fetch()
返回一个 promise,您可以使用 await
语法获取响应。您可以调用响应对象的 json()
方法来获取 json 数据。
getQuotes()
返回一个 promise,它将解析为 JSON 数据。
由于 getQuotes()
函数使用了 await
关键字,因此它必须是一个 async
函数。
showQuotes() 函数
以下定义了 showQuotes()
函数,该函数从 quotes
数组生成 <blockquote>
元素并将它们追加到 quotes
元素
// show the quotes
const showQuotes = (quotes) => {
quotes.forEach(quote => {
const quoteEl = document.createElement('blockquote');
quoteEl.classList.add('quote');
quoteEl.innerHTML = `
<span>${quote.id})</span>
${quote.quote}
<footer>${quote.author}</footer>
`;
quotesEl.appendChild(quoteEl);
});
};
Code language: JavaScript (javascript)
工作原理
showQuotes()
使用 forEach()
方法遍历 quotes
数组。
对于每个引号对象,它创建一个类名为 quote
的 <blockquote>
元素
<blockquote class="quote">
</blockquote>
Code language: HTML, XML (xml)
它使用 模板字面量 语法生成引号对象的 HTML 表示形式。它将 HTML 添加到 <blockquote>
元素中。
以下是生成的 <blockquote>
元素的示例
<blockquote class="quote">
<span>1)</span>
Talk is cheap. Show me the code.
<footer>Linus Torvalds</footer>
</blockquote>
Code language: HTML, XML (xml)
在每次迭代结束时,该函数使用 appendChild()
方法将 <blockquote>
元素追加到 quotesEl
元素的子元素中。
显示/隐藏加载指示器函数
以下定义了两个显示和隐藏加载指示器元素的函数
const hideLoader = () => {
loader.classList.remove('show');
};
const showLoader = () => {
loader.classList.add('show');
};
Code language: JavaScript (javascript)
加载指示器的透明度为 0,默认情况下不可见。.show
类将加载指示器的透明度设置为 1,这将使其可见。
要隐藏加载指示器,您需要从加载指示器元素中删除 show
类。类似地,要显示加载指示器,您需要将其 show
类添加到其类列表中。
定义控制变量
以下声明了 currentPage
变量并将其初始化为 1
let currentPage = 1;
Code language: JavaScript (javascript)
当您向下滚动到页面底部时,应用程序将发出 API 请求以获取下一个引号。在此之前,您需要将 currentPage
变量增加 1。
要指定您一次想要获取的引号数量,您可以使用类似这样的常量
const limit = 10;
Code language: JavaScript (javascript)
以下 total
变量存储从 API 返回的引号总数
let total = 0;
Code language: JavaScript (javascript)
hasMoreQuotes() 函数
以下 hasMoreQuotes()
函数在以下情况下返回 true
- 这是第一次获取 (
total === 0
) - 或者还有更多引号要从 API 获取 (
startIndex
<total
)
const hasMoreQuotes = (page, limit, total) => {
const startIndex = (page - 1) * limit + 1;
return total === 0 || startIndex < total;
};
Code language: JavaScript (javascript)
loadQuotes() 函数
以下定义了一个执行四个操作的函数
- 显示加载指示器。
- 如果还有更多引号要获取,则通过调用
getQuotes()
函数从 API 获取引号。 - 在页面上显示引号。
- 隐藏加载指示器。
// load quotes
const loadQuotes = async (page, limit) => {
// show the loader
showLoader();
try {
// if having more quotes to fetch
if (hasMoreQuotes(page, limit, total)) {
// call the API to get quotes
const response = await getQuotes(page, limit);
// show quotes
showQuotes(response.data);
// update the total
total = response.total;
}
} catch (error) {
console.log(error.message);
} finally {
hideLoader();
}
};
Code language: JavaScript (javascript)
如果 getQuotes()
函数执行速度很快,您将看不到加载指示器。
为了确保加载指示器始终显示,您可以使用 setTimeout()
函数
// load quotes
const loadQuotes = async (page, limit) => {
// show the loader
showLoader();
// 0.5 second later
setTimeout(async () => {
try {
// if having more quotes to fetch
if (hasMoreQuotes(page, limit, total)) {
// call the API to get quotes
const response = await getQuotes(page, limit);
// show quotes
showQuotes(response.data);
// update the total
total = response.total;
}
} catch (error) {
console.log(error.message);
} finally {
hideLoader();
}
}, 500);
};
Code language: JavaScript (javascript)
通过添加 setTimeout()
函数,加载指示器将至少显示半秒钟。您可以通过更改 setTimeout()
函数的第二个参数来调整延迟时间。
附加滚动事件
要使用户滚动到页面底部时加载更多引号,您需要附加一个 滚动事件处理程序。
如果满足以下条件,滚动事件处理程序将调用 loadQuotes()
函数
- 首先,滚动位置在页面底部。
- 其次,还有更多引号要获取。
滚动事件处理程序还将在加载下一个引号之前增加 currentPage
变量。
window.addEventListener('scroll', () => {
const {
scrollTop,
scrollHeight,
clientHeight
} = document.documentElement;
if (scrollTop + clientHeight >= scrollHeight - 5 &&
hasMoreQuotes(currentPage, limit, total)) {
currentPage++;
loadQuotes(currentPage, limit);
}
}, {
passive: true
});
Code language: JavaScript (javascript)
初始化页面
当页面第一次加载时,您需要调用 loadQuotes()
函数来加载第一批引号
loadQuotes(currentPage, limit);
将 app.js 代码包装在 IIFE 中
为了避免您定义的变量和函数发生冲突,您可以将 app.js
文件中的所有代码包装在一个 IIFE 中。
最终的 app.js
将如下所示
(function () {
const quotesEl = document.querySelector('.quotes');
const loaderEl = document.querySelector('.loader');
// get the quotes from API
const getQuotes = async (page, limit) => {
const API_URL = `https://api.javascripttutorial.net/v1/quotes/?page=${page}&limit=${limit}`;
const response = await fetch(API_URL);
// handle 404
if (!response.ok) {
throw new Error(`An error occurred: ${response.status}`);
}
return await response.json();
}
// show the quotes
const showQuotes = (quotes) => {
quotes.forEach(quote => {
const quoteEl = document.createElement('blockquote');
quoteEl.classList.add('quote');
quoteEl.innerHTML = `
<span>${quote.id})</span>
${quote.quote}
<footer>${quote.author}</footer>
`;
quotesEl.appendChild(quoteEl);
});
};
const hideLoader = () => {
loaderEl.classList.remove('show');
};
const showLoader = () => {
loaderEl.classList.add('show');
};
const hasMoreQuotes = (page, limit, total) => {
const startIndex = (page - 1) * limit + 1;
return total === 0 || startIndex < total;
};
// load quotes
const loadQuotes = async (page, limit) => {
// show the loader
showLoader();
// 0.5 second later
setTimeout(async () => {
try {
// if having more quotes to fetch
if (hasMoreQuotes(page, limit, total)) {
// call the API to get quotes
const response = await getQuotes(page, limit);
// show quotes
showQuotes(response.data);
// update the total
total = response.total;
}
} catch (error) {
console.log(error.message);
} finally {
hideLoader();
}
}, 500);
};
// control variables
let currentPage = 1;
const limit = 10;
let total = 0;
window.addEventListener('scroll', () => {
const {
scrollTop,
scrollHeight,
clientHeight
} = document.documentElement;
if (scrollTop + clientHeight >= scrollHeight - 5 &&
hasMoreQuotes(currentPage, limit, total)) {
currentPage++;
loadQuotes(currentPage, limit);
}
}, {
passive: true
});
// initialize
loadQuotes(currentPage, limit);
})();
Code language: JavaScript (javascript)
这是最终版本 的 web 应用程序。