What is infinite scrolling?
Infinite scrolling is a feature used to dynamically load more content on a page once a user scrolls to the bottom of the page.
The concept of infinite scrolling is used to load data from a server in a way that appears “seamless” to a user but does not overload the server by requesting too much data at one time.
In a previous tutorial, we implemented a pagination feature that allowed us to break down our content into navigable sections known as pages. This tutorial will use a similar implementation.
Advantages of vanilla JavaScript
A significant advantage of using JavaScript is that our implementation is framework independent, meaning it doesn’t depend on any framework, so it can be modified to work on all of them.
Additionally, as we are building the functionality ourselves and are not dependent 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 all the way down the pen to upload more content:
1. Markup with HTML
We will start by placing the container for our cards on the page. We will add the cards to the container using JavaScript so that the div is empty.
<div id="card-container"></div>
We also have a charger div
to see an animation before adding the next batch of cards and an action card div
to show 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 add to the card-container div will have a class name ‘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 will also create a loading animation for the skeleton cards in the loader div by animating the file ::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%); } }
Affordable style
Whenever we include an animation on a web page, it is important to consider the implications for accessibility. Some users may prefer not to have any animation and we can take this preference into account in our style 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 content limit to load on the page.
- Detects when the user has reached the end of the content container.
- Load more content once you reach the end of the container.
- If there is no more content to load, stop infinite scrolling.
Define the 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 will need a value for the maximum number of cards to add to the page. If you are receiving your data from a server, this value is the length of the response from the server. We initialize a card limit of 99.
const cardLimit = 99;
The cardTotalElem
is the item to display the maximum number of cards on the page so you can set the innerHTML
to the cardLimit
value;
cardTotalElem.innerHTML = cardLimit;
So we will define a variable for how many cards we want to increase the page by:
const cardIncrease = 9;
We would like to know how many “pages” we will have, ie how many times we can increase the content until we reach the maximum limit. For example, with our defined cardLimit
And cardIncrease
variables, we can increase the content 10 times (assuming we have already loaded the first 9 elements) until we reach the limit. We will do this by dividing the cardLimit
from the cardIncrease
.
const pageCount = Math.ceil(cardLimit / cardIncrease);
We will then define a value to determine which page we are on:
let currentPage = 1;
Creating a new card
Now we have all our constants, let’s create a function to add a new card to the card container. We will set the innerHTML
of our cards to the index value so that we can keep track of the number of cards we are adding.
A fun feature of 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
We will now add our cards to our container using features similar to the layout tutorial.
First, determine the range of cards to add to the page. The addCards
the function will accept a pageIndex
parameter, which will update the global currentPage
value. If we are on page 1, we will add cards 1 to 9. If we are on page 2, we will add cards 10 to 18 and so on.
We can mathematically define it 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 starting range will always be one less than the value we are trying to get (i.e. on page 1, the starting range is 0, on page 2, the starting range is 9) so we will take that into account by setting the value of our for su loop index startRange + 1
.
Detect when the card limit is reached
One limit that we will have to pay attention to is the endRange
number. If we’re on the last page, we’ll want our final range to be the same as cardLimit
. For example, if we have a cardLimit
of 75 ea cardIncrease
of 10 and we are on page 8, our starting index will be 70 and ours endRange
the value should be 75.
We will modify ours addCards
function to take this into account:
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 shows the number of cards currently shown on the page, so we will set the innerHTML
of this element as the final 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 the starting cards
We have defined a functionality for adding cards to the container, so we will include a window.onload
function to set the initial cards to add to the page.
window.onload = function () { addCards(currentPage); };
Handle infinite scrolling
We will manage our infinite scroll by increasing the currentPage
number to add new cards to the container when we have reached the end of the page. We can detect when the end of the page is reached by adding the file 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 is a visual representation of what it looks like:
Once we reach 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); } };
So let’s create an event listener for window scrolling and pass our function over it:
window.addEventListener("scroll", handleInfiniteScroll);
Performance optimization
Since we are working with the scroll event listener, it is beneficial for the performance of our webpage to limit the number of calls made. We can slow down the number of calls by using an acceleration function.
We will define our acceleration function like this:
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); };
Stop infinite scroll
At this point, we’ve set up our functions to add more content once we reach the bottom of the page. Now, let’s make sure our function stops working when there is no more content to add, such as when the cardLimit
is reached.
First, let’s define ours removeInfiniteScroll
function. In this function, we will remove the handleInfiniteScroll
function from the scroll event listener and also delete the loader div.
const removeInfiniteScroll = () => { loader.remove(); window.removeEventListener("scroll", handleInfiniteScroll); };
We will now modify ours handleInfiniteScroll
to take into account if there is no more content to add, i.e. we are on the last content page.
const handleInfiniteScroll = () => { throttle(() => { const endOfPage = window.innerHeight + window.pageYOffset >= document.body.offsetHeight; if (endOfPage) { addCards(currentPage + 1); } if (currentPage === pageCount) { removeInfiniteScroll(); } }, 1000); };
Conclusion
And here we are! We have created an efficient and accessible implementation of the infinite scroll function. Check the complete JavaScript code by pressing the button JS tab on the demo embedded below: