在本教程中,我们将看到一个使用无限滚动方法分解页面内容的简单实现。我们将使用 html、css 和 vanilla JAVAScript 来构建无限滚动功能的高性能且可访问的版本。
无限滚动是一种用于在用户滚动到页面末尾时在页面上动态加载更多内容的功能。
无限滚动的概念用于以一种用户感觉“无缝”的方式从服务器加载数据,但不会因为一次请求太多数据而使服务器过载。
在之前的教程中,我们实现了分页功能,它允许我们将内容分解为称为页面的可导航部分。本教程将使用类似的实现。
使用 JavaScript 的一个显着好处是我们的实现与框架无关,即它不依赖于任何框架,因此可以对其进行修改以在所有框架上工作。
此外,由于我们自己构建功能而不依赖于插件,因此我们可以确保实现是轻量级的并且完全适合我们的需求。
这是最终产品的外观,滚动到笔的底部以加载更多内容:
我们将从在页面上放置卡片的容器开始。我们将使用 JavaScript 将卡片添加到容器中,因此 div 将为空。
<div id="card-container"></div>
我们还有一个加载器div,用于在添加下一批卡片之前显示动画,以及div用于显示卡片数量和卡片总数的卡片操作。
<div id="loader">
<div class="skeleton-card"></div>
<div class="skeleton-card"></div>
<div class="skeleton-card"></div>
</div>
<div class="card-actions">
<span>Showing
<span id="card-count"></span> of
<span id="card-total"></span> cards
</span>
</div>
加载器和卡片计数 div 的外观
我们将添加到 card-container div 中的卡片将有一个类名“card”。
#card-container {
display: flex;
flex-wrap: wrap;
}
.card {
height: 55vh;
width: calc((100% / 3) - 16px);
margin: 8px;
border-radius: 3px;
transition: all 200ms ease-in-out;
display: flex;
align-items: center;
justify-content: center;
}
.card:hover {
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);
}
.card-actions {
margin: 8px;
padding: 16px 0;
display: flex;
justify-content: space-between;
align-items: center;
}
我们还将通过动画::after伪选择器为加载器 div 中的骨架卡创建加载动画:
#loader {
display: flex;
}
.skeleton-card {
height: 55vh;
width: calc((100% / 3) - 16px);
margin: 8px;
border-radius: 3px;
transition: all 200ms ease-in-out;
position: relative;
background-color: #eaeaea;
}
.skeleton-card::after {
content: "";
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
transform: translateX(-100%);
background-image: linear-gradient(90deg, rgba(255, 255, 255, 0) 0, rgba(255, 255, 255, 0.2) 20%, rgba(255, 255, 255, 0.5) 60%, rgba(255, 255, 255, 0));
animation: load 1s infinite;
}
@keyframes load {
100% {
transform: translateX(100%);
}
}
每当我们在网页上包含动画时,重要的是要考虑可访问性的影响。有些用户可能更喜欢根本没有动画,我们可以通过使用媒体规则在样式中考虑这种偏好,prefers-reduced-motion
@media screen and (prefers-reduced-motion: reduce) {
.skeleton-card::after {
animation: none;
}
}
让我们分解无限滚动背后的逻辑。
首先,让我们从 DOM 中获取我们需要的所有元素:
const cardContainer = document.getElementById("card-container");
const cardCountElem = document.getElementById("card-count");
const cardTotalElem = document.getElementById("card-total");
const loader = document.getElementById("loader");
现在我们需要定义我们的全局变量。
我们需要一个要添加到页面的最大卡片数量的值。如果您从服务器获取数据,则此值是服务器响应的长度。让我们初始化一个 99 的卡片限制。
const cardLimit = 99;
cardTotalElem 是用于在页面上显示最大卡片数量的元素,因此我们可以将 设置为innerHTML值cardLimit;
cardTotalElem.innerHTML = cardLimit;
然后我们将定义一个变量来表示要增加页面的卡片数量:
const cardIncrease = 9;
我们想知道我们将拥有多少“页面”,即我们可以增加多少次内容,直到达到最大限制。例如,使用我们定义的cardLimit和cardIncrease变量,我们可以将内容增加 10 倍(假设我们已经加载了前 9 个元素),直到达到限制。我们将通过除以 来做到这cardLimit一点cardIncrease。
const pageCount = Math.ceil(cardLimit / cardIncrease);
然后我们将定义一个值来确定我们在哪个页面上:
let currentPage = 1;
现在我们有了所有的常量,让我们创建一个函数来向卡片容器中添加一张新卡片。我们将innerHTML卡片的 设置为索引值,这样我们就可以跟踪我们正在添加的卡片数量。
此演示中的一个有趣功能是每张卡片都有随机生成的背景颜色。
const getRandomColor = () => {
const h = Math.floor(Math.random() * 360);
return `hsl(${h}deg, 90%, 85%)`;
};
const createCard = (index) => {
const card = document.createElement("div");
card.className = "card";
card.innerHTML = index;
card.style.backgroundColor = getRandomColor();
cardContainer.AppendChild(card);
};
现在我们将使用与分页教程类似的功能将卡片添加到容器中。
首先,确定要添加到页面的卡片范围。该addCards函数将接受一个pageIndex参数,该参数将更新全局currentPage值。如果我们在第 1 页,我们将添加卡片 1 到 9。如果我们在第 2 页,我们将添加卡片 10 到 18,依此类推。
我们可以在数学上将其定义为:
const addCards = (pageIndex) => {
currentPage = pageIndex;
const startRange = (pageIndex - 1) * cardIncrease;
const endRange = pageIndex * cardIncrease;
for (let i = startRange + 1; i <= currRange; i++) {
createCard(i);
}
};
在这个函数中,我们的开始范围总是比我们试图获得的值小一(即在第 1 页,开始范围是 0,在第 2 页,开始范围是 9)所以我们将考虑这一点通过将我们的 for 循环索引的值设置为startRange + 1.
我们必须注意的一个限制是endRange数量。如果我们在最后一页,我们希望结束范围与cardLimit. 例如,如果我们有cardLimit75 和cardIncrease10 的 a 并且我们在第 8 页,我们的起始索引将是 70,我们的endRange值应该是 75。
我们将修改我们的addCards函数来解决这个问题:
const addCards = (pageIndex) => {
currentPage = pageIndex;
const startRange = (pageIndex - 1) * cardIncrease;
const endRange = currentPage == pageCount ? cardLimit : pageIndex * cardIncrease;
for (let i = startRange + 1; i <= endRange; i++) {
createCard(i);
}
};
我们的演示还包括一个cardTotal元素,该元素显示页面上当前显示的卡片数量,因此我们innerHTML将此元素的 设置为结束范围。
const addCards = (pageIndex) => {
currentPage = pageIndex;
const startRange = (pageIndex - 1) * cardIncrease;
const endRange = currentPage == pageCount ? cardLimit : pageIndex * cardIncrease;
cardCountElem.innerHTML = endRange;
for (let i = startRange + 1; i <= endRange; i++) {
createCard(i);
}
};
我们已经定义了将卡片添加到容器的功能,因此我们将包含一个window.onload函数来设置要添加到页面的初始卡片。
window.onload = function () {
addCards(currentPage);
};
currentPage当我们到达页面末尾时,我们将通过增加向容器添加新卡片的数量来处理无限滚动。innerHeight我们可以通过将窗口的 值添加到滚动值pageYOffset并将其与offsetHeight作为页面总高度的文档进行比较来检测何时到达页面末尾。
这是它的外观的视觉表示:
一旦我们到达页面的末尾,我们想通过调用addCardscurrentPage + 1 的函数来加载一个新页面。
const handleInfiniteScroll = () => {
const endOfPage = window.innerHeight + window.pageYOffset >= document.body.offsetHeight;
if (endOfPage) {
addCards(currentPage + 1);
}
};
然后我们为窗口滚动创建一个事件监听器,并将上面的函数传递给它:
window.addEventListener("scroll", handleInfiniteScroll);
由于我们正在使用滚动事件侦听器,因此限制调用次数对我们网页的性能是有益的。我们可以使用节流功能减慢调用次数。
我们将这样定义我们的节流函数:
var throttleTimer;
const throttle = (callback, time) => {
if (throttleTimer) return;
throttleTimer = true;
setTimeout(() => {
callback();
throttleTimer = false;
}, time);
};
然后我们将油门函数传递给handleInfiniteScroll函数
const handleInfiniteScroll = () => {
throttle(() => {
const endOfPage =
window.innerHeight + window.pageYOffset >= document.body.offsetHeight;
if (endOfPage) {
addCards(currentPage + 1);
}
}, 1000);
};
至此,我们已经设置了函数,以便在到达页面末尾时添加更多内容。现在,让我们确保我们的函数在没有要添加的内容时停止运行,即cardLimit到达时。
首先,让我们定义我们的removeInfiniteScroll函数。在此函数中,我们将从handleInfiniteScroll滚动事件侦听器中删除该函数,并删除加载器 div。
const removeInfiniteScroll = () => {
loader.remove();
window.removeEventListener("scroll", handleInfiniteScroll);
};
现在我们将修改我们handleInfiniteScroll以考虑是否没有更多内容要添加,即我们在内容的最后一页。
const handleInfiniteScroll = () => {
throttle(() => {
const endOfPage =
window.innerHeight + window.pageYOffset >= document.body.offsetHeight;
if (endOfPage) {
addCards(currentPage + 1);
}
if (currentPage === pageCount) {
removeInfiniteScroll();
}
}, 1000);
};
我们终于得到它了!我们已经构建了无限滚动功能的高性能且可访问的实现。