What is Infinite Scrolling?
Infinite scrolling is a feature used to dynamically load more content on a page once a user scrolls to the end of the page.
The concept of infinite scrolling is used to load data from a server in a way that feels “seamless” to a user but doesn’t overload the server by requesting too much data at once.
In a previous tutorial, we implemented a pagination feature which allowed us break up our content into navigable sections known as pages. This tutorial will use a similar implementation.
Benefits of Vanilla JavaScript
A significant benefit of using JavaScript is that our implementation is framework-agnostic i.e. it’s not dependent on any framework so it can be modded to work across all of them.
Also, since we’re building the feature ourselves and not depending on a plugin, we can ensure that the implementation is lightweight and perfectly suited to our needs.
Here’s a look at the final product, scroll to the bottom of the pen to load more content:
1. Markup with HTML
We’ll start by placing the container for our cards on the page. We’ll be adding the cards to the container using JavaScript so the div will be empty.
<div id="card-container"></div>
We also have a loader div
for displaying an animation before adding the next batch of cards, and a card-actions div
for showing the card count and card total.
<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>
2. Styling with CSS
The cards we’ll be adding to the card-container div will have a classname of ‘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; }
We’ll also create a loading animation for the skeleton cards in the loader div by animating the ::after
pseudo-selector:
#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%); } }
Accessible Styling
Whenever we’re including an animation on a webpage, it’s important to consider the accessibility implications. Some users may prefer to have no animation at all and we can take that preference into consideration in our styling by using the media rule, prefers-reduced-motion
@media screen and (prefers-reduced-motion: reduce) { .skeleton-card::after { animation: none; } }
3. Functionality With JavaScript
Let’s break down the logic behind infinite scrolling.
- Define the limit of the content to be loaded on the page.
- Detect when the user has reached the end of the content container.
- Load more content once the end of the container has been reached.
- If there’s no more content to be loaded, stop the infinite scroll.
Defining Constants
First, let’s get all the elements we’ll need from our DOM:
const cardContainer = document.getElementById("card-container"); const cardCountElem = document.getElementById("card-count"); const cardTotalElem = document.getElementById("card-total"); const loader = document.getElementById("loader");
Now we need to define our global variables.
We’ll need a value for the max number of cards to be added to the page. If you’re getting your data from a server, this value is the length of the response from the server. Let’s initialise a card limit of 99.
const cardLimit = 99;
The cardTotalElem
is the element for displaying the max number of cards on the page so we can set the innerHTML
to the cardLimit
value;
cardTotalElem.innerHTML = cardLimit;
Then we’ll define a variable for how many cards we want to increase the page by:
const cardIncrease = 9;
We’ll want to know how many “pages” we’ll have i.e. how many times can we increase the content till we reach the max limit. For example, with our defined cardLimit
and cardIncrease
variables, we can increase the content 10 times (assuming we’ve already loaded the first 9 elements) until we reach the limit. We’ll do this by dividing the cardLimit
by the cardIncrease
.
const pageCount = Math.ceil(cardLimit / cardIncrease);
Then we’ll define a value to determine which page we’re on:
let currentPage = 1;
Creating a New Card
Now we have all our constants, let’s make a function to add a new card to the card container. We’ll set the innerHTML
of our cards to the index value so we can keep track of the number of cards we’re adding.
A fun feature in this demo is that each card has a randomly generated background color.
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); };
Adding Cards to the Container
Now we’ll add our cards to our container using similar functionality to the Pagination tutorial.
First, determine the range of cards to be added to the page. The addCards
function will accept a pageIndex
parameter, which will update the global currentPage
value. If we’re on page 1, we’ll add cards 1 to 9. If we’re on page 2, we’ll add cards 10 to 18 and so on.
We can define that mathematically as:
const addCards = (pageIndex) => { currentPage = pageIndex; const startRange = (pageIndex - 1) * cardIncrease; const endRange = pageIndex * cardIncrease; for (let i = startRange + 1; i <= currRange; i++) { createCard(i); } };
In this function, our start range will always be one less than the value we’re trying to get (i.e. on page 1, the start range is 0, on page 2, the start range is 9) so we’ll account for that by setting the value of our for loop index to startRange + 1
.
Detecting When Card Limit is Reached
A limit we’ll have to look out for is the endRange
number. If we’re on the last page, we’ll want our end range to be the same as the cardLimit
. For instance, if we have a cardLimit
of 75 and a cardIncrease
of 10 and we’re on page 8, our starting index will be 70 and our endRange
value should be 75.
We’ll modify our addCards
function to account for this:
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); } };
Our demo also includes a cardTotal
element that displays the number of cards currently being shown on the page so we’ll set the innerHTML
of this element as the end range.
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); } };
Loading Initial Cards
We’ve defined a feature for adding cards to the container so we’ll include a window.onload
function to set the initial cards to be added to the page.
window.onload = function () { addCards(currentPage); };
Handling Infinite Scroll
We’ll handle our infinite scroll by increasing the currentPage
number to add new cards to the container when we’ve reached the end of the page. We can detect when the end of the page is reached by adding the innerHeight
of the window to the scroll value pageYOffset
and comparing it to the document offsetHeight
which is the total height of the page.
Here’s a visual representation of what this looks like:
Once we’ve reached the end of the page, we want to load a new page by calling our addCards
function with currentPage + 1.
const handleInfiniteScroll = () => { const endOfPage = window.innerHeight + window.pageYOffset >= document.body.offsetHeight; if (endOfPage) { addCards(currentPage + 1); } };
Then we create an event listener for the window scroll and pass our above function into it:
window.addEventListener("scroll", handleInfiniteScroll);
Performance Optimisation
Since we’re working with the scroll event listener, it’s beneficial to the performance of our webpage to limit the number of calls made. We can slow down the number of calls using a throttle function.
We’ll define our throttle function this way:
var throttleTimer; const throttle = (callback, time) => { if (throttleTimer) return; throttleTimer = true; setTimeout(() => { callback(); throttleTimer = false; }, time); };
and then we pass the throttle function into the handleInfiniteScroll
function
const handleInfiniteScroll = () => { throttle(() => { const endOfPage = window.innerHeight + window.pageYOffset >= document.body.offsetHeight; if (endOfPage) { addCards(currentPage + 1); } }, 1000); };
Stopping Infinite Scroll
At this point, we’ve set up our functions to add more content once the end of the page is reached. Now, let’s make sure our function stops running when there’s no more content to be added i.e. when the cardLimit
is reached.
First, let’s define our removeInfiniteScroll
function. In this function, we’ll remove the handleInfiniteScroll
function from the scroll event listener and also delete the loader div.
const removeInfiniteScroll = () => { loader.remove(); window.removeEventListener("scroll", handleInfiniteScroll); };
Now we’ll modify our handleInfiniteScroll
to account for if there’s no more content to be added i.e. we’re on the last page of content.
const handleInfiniteScroll = () => { throttle(() => { const endOfPage = window.innerHeight + window.pageYOffset >= document.body.offsetHeight; if (endOfPage) { addCards(currentPage + 1); } if (currentPage === pageCount) { removeInfiniteScroll(); } }, 1000); };
Conclusion
And there we have it! We’ve built a performant and accessible implementation of the infinite scroll feature. Check out the complete JavaScript code by hitting the JS tab on the embedded demo below: