Lexington has been awarded a grant from Astro, to celebrate. Get a 30% discount. Apply code LEXINGTON30 ( uppercase ) at checkout.

← Back to all tutorials

How to create infinite scrolling with Tailwind CSS and JavaScript

js-infinite-scroll
Published and written on Aug 27 2024 by Michael Andreuzza

Hello there! Let’s dive into creating an infinite scrolling feature with Tailwind CSS and JavaScript.

What is infinite scrolling?

Well, it’s a technique that allows users to scroll through content without reaching the end. It’s a great way to keep users engaged and provide a seamless browsing experience. In this tutorial, we’ll explore how to implement infinite scrolling using Tailwind CSS and JavaScript.

Use cases

Infinite scrolling is commonly used in websites for various purposes, such as:

  • News feeds: Displaying a continuous stream of articles or posts.
  • Product listings: Showing a large number of items without overwhelming the user.
  • Image galleries: Allowing users to scroll through a collection of images without reaching the end.
  • Content sections: Providing a continuous flow of content within a section, such as a blog post or article. and many other uses cases.

Let’s get started by writting the markup and understanding the basic structure

The content

  • id="content": This is the container for the dynamic content that will be appended to it.
  • aria-live="polite": This attribute is used to indicate that the content is live and should be announced to screen readers. It helps users with disabilities to understand the content as it is being loaded.

The loader

  • id="loader": This is the container for the loader element. It will be displayed while the content is being loaded. The loader can be a simple spinner or a loading indicator.

The load more button

  • id="load-more": This is the button that will be used to trigger the loading of more content. It will be hidden until the user scrolls to the bottom of the page.

Note: Styling classes are omitted for brevity

<div>
  <div id="content" aria-live="polite">
    <!-- Dynamic content will be appended here -->
  </div>
  <div id="loader">
      <!--- Spinner or loading indicator will be displayed here -->
  </div>
  <div>
    <button id="load-more">Load More</button>
  </div>
</div>

The JavaScript

Now that we have the basic structure in place, let’s write the JavaScript code to make it work. We’ll use the IntersectionObserver API to detect when the user scrolls to the bottom of the page and trigger the loading of more content.

The addEventListener

  • DOMContentLoaded: This event is triggered when the DOM (Document Object Model) is fully loaded and parsed.
  • const content = document.getElementById("content");: This line of code selects the content element by its ID and stores it in a variable called content.
  • const loader = document.getElementById("loader");: This line of code selects the loader element by its ID and stores it in a variable called loader.
  • const loadMoreButton = document.getElementById("load-more");: This line of code selects the load more button element by its ID and stores it in a variable called loadMoreButton.
  • let isLoading = false;: This line of code initializes a variable called isLoading to false.
  • let page = 1;: This line of code initializes a variable called page to 1.
document.addEventListener("DOMContentLoaded", function() {
    const content = document.getElementById("content");
    const loader = document.getElementById("loader");
    const loadMoreButton = document.getElementById("load-more");
    let isLoading = false;
    let page = 1;
    // Rest of the code goes here
});

The loadMoreContent function

  • function loadMoreContent(): This is a function that will be called when the user clicks the load more button.
  • if (isLoading) return;: This line of code checks if the isLoading variable is true.
  • isLoading = true;: This line of code sets the isLoading variable to true.
  • loader.classList.remove("hidden");: This line of code removes the hidden class from the loader element.
  • loadMoreButton.disabled = true;: This line of code disables the load more button.
function loadMoreContent() {
        if (isLoading) return;
        isLoading = true;
        loader.classList.remove("hidden");
        loadMoreButton.disabled = true;
        // Rest of the code goes here

The setTimeout function

  • setTimeout(() => {: This line of code sets a timeout for the function. The function will be executed after a certain amount of time.
  • for (let i = 0; i < 20; i++) {: This line of code creates a loop that will iterate 20 times.
  • const item = document.createElement("div");: This line of code creates a new div element and stores it in a variable called item.
  • item.className = "p-4 bg-white rounded-lg shadow-xl shadow-neutral-500/30 text-center border";: This line of code sets the classes of the item element.
  • item.textContent = Item ${(page - 1) * 25 + i + 1};: This line of code sets the text content of the item element. It uses a template literal to generate a unique item number based on the current page number and the loop index.
  • content.appendChild(item);: This line of code appends the item element to the content element.
  • loader.classList.add("hidden");: This line of code adds the hidden class to the loader element.
  • isLoading = false;: This line of code sets the isLoading variable to false.
  • loadMoreButton.disabled = false;: This line of code enables the load more button.
  • page++;: This line of code increments the page variable by 1.

Announce to screen readers that new content has been loaded

  • const announcement = document.createElement("div");: This line of code creates a new div element and stores it in a variable called announcement.
  • announcement.textContent = "New items have been loaded.";: This line of code sets the text content of the announcement element.
  • announcement.setAttribute("role", "status");: This line of code sets the role attribute of the announcement element to “status”.
  • announcement.setAttribute("aria-live", "polite");: This sets the aria-live attribute of the announcement element to “polite”.
  • announcement.style.position = "absolute";: This sdks the position style of the announcement element to “absolute”.
  • announcement.style.left = "-9999px";: This sets the left style of the announcement element to “-9999px”.
  • document.body.appendChild(announcement);: This line of code appends the announcement element to the body of the document.
  • setTimeout(() => document.body.removeChild(announcement), 1000);: This line of code removes the announcement element after a delay of 1000 milliseconds.
  • }, 1000);: This line of code sets a timeout for the function.
 setTimeout(() => {
    for (let i = 0; i < 20; i++) {
        const item = document.createElement("div");
        item.className =
            "p-4 bg-white rounded-lg shadow-xl shadow-neutral-500/30 text-center border";
        item.textContent = `Item ${(page - 1) * 25 + i + 1}`;
        content.appendChild(item);
    }
    loader.classList.add("hidden");
    isLoading = false;
    loadMoreButton.disabled = false;
    page++;

    // Announce to screen readers that new content has been loaded
    const announcement = document.createElement("div");
    announcement.textContent = "New items have been loaded.";
    announcement.setAttribute("role", "status");
    announcement.setAttribute("aria-live", "polite");
    announcement.style.position = "absolute";
    announcement.style.left = "-9999px";
    document.body.appendChild(announcement);
    setTimeout(() => document.body.removeChild(announcement), 1000);
}, 1000);
}

The IntersectionObserver

  • const observer = new IntersectionObserver: This line of code creates a new IntersectionObserver object and stores it in a variable called observer.
  • (entries) => {: This line of code defines a callback function that will be called when the IntersectionObserver detects an intersection.
  • if (entries[0].isIntersecting && !isLoading) {: This line of code checks if the first entry in the entries array is intersecting and if the isLoading variable is false.
  • loadMoreContent();: This line of code calls the loadMoreContent function.
  • }, {: This line of code starts a new object literal.
  • rootMargin: "100px": This line of code sets the rootMargin property of the IntersectionObserver object to “100px”.
 const observer = new IntersectionObserver(
        (entries) => {
            if (entries[0].isIntersecting && !isLoading) {
                loadMoreContent();
            }
        }, {
            rootMargin: "100px"
        }
    );

The addEventListener

  • observer.observe(loader);: This line of code observes the loader element using the observer object.
  • loadMoreButton.addEventListener("click", loadMoreContent);: This line of code adds an event listener to the load more button that calls the loadMoreContent function when the button is clicked.
  • loadMoreContent();: This line of code calls the loadMoreContent function immediately.
   observer.observe(loader);

    // Event listener for the Load More button
    loadMoreButton.addEventListener("click", loadMoreContent);

    // Initial load
    loadMoreContent();

Here is the complete code for the infinite scrolling

document.addEventListener("DOMContentLoaded", function() {
    const content = document.getElementById("content");
    const loader = document.getElementById("loader");
    const loadMoreButton = document.getElementById("load-more");
    let isLoading = false;
    let page = 1;

    function loadMoreContent() {
        if (isLoading) return;
        isLoading = true;
        loader.classList.remove("hidden");
        loadMoreButton.disabled = true;

        // Simulate an API call with setTimeout
        setTimeout(() => {
            for (let i = 0; i < 20; i++) {
                const item = document.createElement("div");
                item.className =
                    "p-4 bg-white rounded-lg shadow-xl shadow-neutral-500/30 text-center border";
                item.textContent = `Item ${(page - 1) * 25 + i + 1}`;
                content.appendChild(item);
            }
            loader.classList.add("hidden");
            isLoading = false;
            loadMoreButton.disabled = false;
            page++;

            // Announce to screen readers that new content has been loaded
            const announcement = document.createElement("div");
            announcement.textContent = "New items have been loaded.";
            announcement.setAttribute("role", "status");
            announcement.setAttribute("aria-live", "polite");
            announcement.style.position = "absolute";
            announcement.style.left = "-9999px";
            document.body.appendChild(announcement);
            setTimeout(() => document.body.removeChild(announcement), 1000);
        }, 1000);
    }

    // Intersection Observer for infinite scroll
    const observer = new IntersectionObserver(
        (entries) => {
            if (entries[0].isIntersecting && !isLoading) {
                loadMoreContent();
            }
        }, {
            rootMargin: "100px"
        }
    );

    observer.observe(loader);

    // Event listener for the Load More button
    loadMoreButton.addEventListener("click", loadMoreContent);

    // Initial load
    loadMoreContent();
});

Conclusion

The infinite scrolling feature is a powerful tool that allows users to explore content in a continuous flow. By using the IntersectionObserver API and a simple JavaScript function, we can create a dynamic and interactive experience that keeps users engaged and entertained.

Hope you enjoyed this tutorial and have a great day!

/Michael Andreuzza

Did you like this tutorial? Please share it with your friends!

Get lifetime access to every theme available today for $199 and own them forever. Plus, new themes, lifetime updates, use on unlimited projects and enjoy lifetime support.

— No subscription required!