Infinite scroll

Bootstrap 5 Infinite scroll

This feature adds a scroll event listener (to the window or the component it's attached to if it has the overflow-y property set to scroll) and calls a callback method every time a user reaches an end of a page/container.

Note: Read the API tab to find all available options and advanced customization


Basic example

Scroll down the container below to add more items.

Note: Your element should be scrollable, for example, it should have overflow-y: scroll property like in the example below.

  • Angry
  • Dizzy
  • Flushed
  • Frown
  • Grimace
  • Grin
        
            
          <div class="infinite-scroll" data-mdb-infinite-direction="y" data-mdb-infinite-container="infinite-scroll-basic">
            <ul
              class="container list-group infinite-scroll infinite-scroll-basic"
              id="basic-example"
              style="max-height: 261px; overflow-y: scroll"
            >
              <li class="list-group-item d-flex align-items-center">
                <i class="far fa-angry fa-3x me-4"></i> Angry
              </li>
              <li class="list-group-item d-flex align-items-center">
                <i class="far fa-dizzy fa-3x me-4"></i> Dizzy
              </li>
              <li class="list-group-item d-flex align-items-center">
                <i class="far fa-flushed fa-3x me-4"></i> Flushed
              </li>
              <li class="list-group-item d-flex align-items-center">
                <i class="far fa-frown fa-3x me-4"></i> Frown
              </li>
              <li class="list-group-item d-flex align-items-center">
                <i class="far fa-grimace fa-3x me-4"></i> Grimace
              </li>
              <li class="list-group-item d-flex align-items-center">
                <i class="far fa-grin fa-3x me-4"></i> Grin
              </li>
            </ul>
          </div>
          
        
    
        
            
          // An array of icon names
          const icons = [
            'Sad-Tear',
            'Meh-Blank',
            'Smile-Wink',
            'Tired',
            'Surprise',
            'Kiss-Beam',
            'Laugh-Squint',
          ];

          // Get a scrollable container using an id attribute
          const basicElement = document.getElementById('basic-example');

          // An index of elements added after scrolling to the end of the container
          let itemIndex = 0;

          // items - an array of the created elements using the loop through icons array
          const items = icons.map((icon) => {
            // Create a list item element
            const element = document.createElement('li');

            // Change HTML code inside created list element using icon we are currently working on
            element.innerHTML = `
              <li class="list-group-item d-flex align-items-center">
                <i class="far fa-${icon.toLowerCase()} fa-3x me-4"></i>${icon}
              </li>
            `;

            // Return ready element
            return element;
          });

          // Add an event listener to the scrollable container. The event below is triggered when a user scrolls to the end of the container
          basicElement.addEventListener('complete.mdb.infiniteScroll', () => {
            // Return nothing when user appended all of the generated items
            if (itemIndex === icons.length - 1) return;

            // Append next element to the scrollable container
            basicElement.appendChild(items[itemIndex]);

            // Increment amount of items that are appended
            itemIndex++;
          });
      
        
    

Direction

Use data-mdb-infinite-direction to define the scrolling direction.

Angry Dizzy Flushed Grimace Grin
        
            
          <div 
            class="infinite-scroll py-3 text-center" 
            id="direction-example"
            style="max-width: 1500px; overflow-x: scroll; white-space: nowrap;" 
            data-mdb-infinite-direction="x"
          >
            <span class="mx-5"><i class="far fa-angry fa-3x me-4"></i> Angry</span>
            <span class="mx-5"><i class="far fa-dizzy fa-3x me-4"></i> Dizzy</span>
            <span class="mx-5"><i class="far fa-flushed fa-3x me-4"></i> Flushed</span>
            <span class="mx-5"><i class="far fa-grimace fa-3x me-4"></i> Grimace</span>
            <span class="mx-5"><i class="far fa-grin fa-3x me-4"></i> Grin</span>
          </div>
        
        
    
        
            
          // Get a scrollable container using an id attribute
          const directionElement = document.getElementById('direction-example');

          // An index of elements added after scrolling to the end of the container
          let itemIndex = 0;

          // An array of icon names
          const icons = [
            'Sad-Tear',
            'Meh-Blank',
            'Smile-Wink',
            'Tired',
            'Surprise',
            'Kiss-Beam',
            'Laugh-Squint',
          ];

          // items - an array of the created elements using the loop through icons array
          const items = icons.map((icon) => {
            // Create a span element
            const element = document.createElement('span');

            // Add class mx-5 to the created element, which defines left and right margin
            element.classList.add('mx-5');

            // Change HTML code inside created span element using icon we are currently working on
            element.innerHTML = `
              <i class="far fa-${icon.toLowerCase()} fa-3x me-4"></i>
              ${icon}
            `;

            // Return ready element
            return element;
          });

          // Add an event listener to the scrollable container. The event below is triggered when a user scrolls to the end of the container
          directionElement.addEventListener('complete.mdb.infiniteScroll', () => {
            // Return nothing when user appended all of the generated items
            if (itemIndex === items.length - 1) return;

            // Append next element to the scrollable container
            directionElement.appendChild(items[itemIndex]);

            // Increment amount of items that are appended
            itemIndex++;
          });
        
        
    

Spinners and asynchronous data

        
            
        <div class="infinite-scroll py-3 text-center" id="spinners-and-async-example" style="max-height: 500px; overflow-y: scroll">
          <div id="images">
            <img src="https://mdbcdn.b-cdn.net/img/Photos/Slides/img%20(100).webp" class="img-fluid mb-3"/>
            <img src="https://mdbcdn.b-cdn.net/img/Photos/Slides/img%20(105).webp" class="img-fluid mb-3"/>
            <img src="https://mdbcdn.b-cdn.net/img/Photos/Slides/img%20(106).webp" class="img-fluid mb-3"/>
          </div>
          <div class="spinner-border mx-auto" id="spinner" style="display: none"></div>
        </div>
        
        
    
        
            
          // Get a spinner, container with images and scrollable container using an id attribute
          const spinner = document.getElementById('spinner');
          const imagesContainer = document.getElementById('images');
          const infiniteContainer = document.getElementById('spinners-and-async-example');

          // Function that generates image with data from API
          const createImg = url => {
            // Create an image element
            let imgElement = document.createElement('img');

            // Add .img-fluid class to the element, it will adjust size of it to the container
            imgElement.classList.add('img-fluid');

            // Set a src attribute using parameter that is passed to the function
            imgElement.setAttribute('src', url);

            // Return ready image element
            return imgElement;
          }

          // Function that loads next image
          const loadImages = () => {
            // Make spinner visible
            spinner.style.display = 'block';

            // Fetch your API
            fetch('YOUR_API/getNextItem')
              .then(response => response.json)
              .then(imgUrl => {
                // Hide spinner after data loads
                spinner.style.display = 'none';

                // Append an image element generated by createImg function to the container with images
                imagesContainer.appendChild(createImg(imgUrl));
              });
          }

          // Add an event listener to the scrollable container. The event below is triggered when a user scrolls to the end of the container
          infiniteContainer.addEventListener('complete.mdb.infiniteScroll', loadImages);
        
        
    

Window

You can apply the mdb.InfiniteScroll instance to a window.

Note: You have to initialize an instance on your own, using JavaScript. If you are using other containers, you have to make a check if your event.target is a window.

        
            
          <!--Main layout-->
          <main class="my-4">
            <div class="container">
              <!--Section: Posts-->
              <section class="text-center mb-4" id="posts">
                <div class="row">
                  <div class="col-md-6 mb-4">
                    <div class="bg-image hover-overlay ripple shadow-1-strong rounded mb-4" data-mdb-ripple-color="light">
                      <img
                        data-mdb-lazy-src="https://mdbcdn.b-cdn.net/img/Photos/Others/images/29.webp"
                        data-mdb-lazy-placeholder="https://placehold.it/1321x583?text=Loading"
                        class="w-100 lazy"
                      />
                      <a href="#!">
                        <div class="mask" style="background-color: rgba(251, 251, 251, 0.2);"></div>
                      </a>
                    </div>
                    <h5>This is an title of the article</h5>
                    <p>
                      Lorem ipsum dolor sit amet consectetur adipisicing elit.
                      Quisquam cupiditate veniam voluptatibus laudantium cum dolorem
                      illo. Quos architecto deserunt saepe.
                    </p>
                    <a class="btn btn-info btn-rounded" href="#!" role="button">Read more</a>
                  </div>

                  <div class="col-md-6 mb-4">
                    <div class="bg-image hover-overlay ripple shadow-1-strong rounded mb-4" data-mdb-ripple-color="light">
                      <img
                        data-mdb-lazy-src="https://mdbcdn.b-cdn.net/img/Photos/Others/images/27.webp"
                        data-mdb-lazy-placeholder="https://placehold.it/1321x583?text=Loading"
                        class="w-100 lazy"
                      />
                      <a href="#!">
                        <div class="mask" style="background-color: rgba(251, 251, 251, 0.2);"></div>
                      </a>
                    </div>
                    <h5>This is an title of the article</h5>
                    <p>
                      Lorem ipsum dolor sit amet consectetur adipisicing elit.
                      Quisquam cupiditate veniam voluptatibus laudantium cum dolorem
                      illo. Quos architecto deserunt saepe.
                    </p>
                    <a class="btn btn-info btn-rounded" href="#!" role="button">Read more</a>
                  </div>
                </div>

                <div class="row">
                  <div class="col-md-6 mb-4">
                    <div class="bg-image hover-overlay ripple shadow-1-strong rounded mb-4" data-mdb-ripple-color="light">
                      <img
                        data-mdb-lazy-src="https://mdbcdn.b-cdn.net/img/Photos/Others/images/25.webp"
                        data-mdb-lazy-placeholder="https://placehold.it/1321x583?text=Loading"
                        class="w-100 lazy"
                      />
                      <a href="#!">
                        <div class="mask" style="background-color: rgba(251, 251, 251, 0.2);"></div>
                      </a>
                    </div>
                    <h5>This is an title of the article</h5>
                    <p>
                      Lorem ipsum dolor sit amet consectetur adipisicing elit.
                      Quisquam cupiditate veniam voluptatibus laudantium cum dolorem
                      illo. Quos architecto deserunt saepe.
                    </p>
                    <a class="btn btn-info btn-rounded" href="#!" role="button">Read more</a>
                  </div>

                  <div class="col-md-6 mb-4">
                    <div class="bg-image hover-overlay ripple shadow-1-strong rounded mb-4" data-mdb-ripple-color="light">
                      <img
                        data-mdb-lazy-src="https://mdbcdn.b-cdn.net/img/Photos/Others/images/24.webp"
                        data-mdb-lazy-placeholder="https://placehold.it/1321x583?text=Loading"
                        class="w-100 lazy"
                      />
                      <a href="#!">
                        <div class="mask" style="background-color: rgba(251, 251, 251, 0.2);"></div>
                      </a>
                    </div>
                    <h5>This is an title of the article</h5>
                    <p>
                      Lorem ipsum dolor sit amet consectetur adipisicing elit.
                      Quisquam cupiditate veniam voluptatibus laudantium cum dolorem
                      illo. Quos architecto deserunt saepe.
                    </p>
                    <a class="btn btn-info btn-rounded" href="#!" role="button">Read more</a>
                  </div>
                </div>

                <div class="row">
                  <div class="col-md-6 mb-4">
                    <div class="bg-image hover-overlay ripple shadow-1-strong rounded mb-4" data-mdb-ripple-color="light">
                      <img
                        data-mdb-lazy-src="https://mdbcdn.b-cdn.net/img/Photos/Others/images/31.webp"
                        data-mdb-lazy-placeholder="https://placehold.it/1321x583?text=Loading"
                        class="w-100 lazy"
                      />
                      <a href="#!">
                        <div class="mask" style="background-color: rgba(251, 251, 251, 0.2);"></div>
                      </a>
                    </div>
                    <h5>This is an title of the article</h5>
                    <p>
                      Lorem ipsum dolor sit amet consectetur adipisicing elit.
                      Quisquam cupiditate veniam voluptatibus laudantium cum dolorem
                      illo. Quos architecto deserunt saepe.
                    </p>
                    <a class="btn btn-info btn-rounded" href="#!" role="button">Read more</a>
                  </div>

                  <div class="col-md-6 mb-4">
                    <div class="bg-image hover-overlay ripple shadow-1-strong rounded mb-4" data-mdb-ripple-color="light">
                      <img
                        data-mdb-lazy-src="https://mdbcdn.b-cdn.net/img/Photos/Others/images/23.webp"
                        data-mdb-lazy-placeholder="https://placehold.it/1321x583?text=Loading"
                        class="w-100 lazy"
                      />
                      <a href="#!">
                        <div class="mask" style="background-color: rgba(251, 251, 251, 0.2);"></div>
                      </a>
                    </div>
                    <h5>This is an title of the article</h5>
                    <p>
                      Lorem ipsum dolor sit amet consectetur adipisicing elit.
                      Quisquam cupiditate veniam voluptatibus laudantium cum dolorem
                      illo. Quos architecto deserunt saepe.
                    </p>
                    <a class="btn btn-info btn-rounded" href="#!" role="button">Read more</a>
                  </div>
                </div>

                <div class="row" id="spinner" style="display: none;">
                  <div class="col-md-12">
                    <div class="spinner-border mx-auto"></div>
                  </div>
                </div>
              </section>
              <!--Section: Posts-->
            </div>
          </main>
          <!--Main layout-->
        
        
    
        
            
          const postsContainer = document.getElementById('posts');
          const spinner = document.getElementById('spinner');

          const items = [
            {
              img: 'https://mdbcdn.b-cdn.net/img/Photos/Others/images/31.webp',
              title: 'This is an title of the article',
              text: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam cupiditate veniam voluptatibus laudantium cum dolorem illo. Quos architecto deserunt saepe.',
            },
            {
              img: 'https://mdbcdn.b-cdn.net/img/Photos/Others/images/23.webp',
              title: 'This is an title of the article',
              text: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam cupiditate veniam voluptatibus laudantium cum dolorem illo. Quos architecto deserunt saepe.',
            },
            {
              img: 'https://mdbcdn.b-cdn.net/img/Photos/Others/images/29.webp',
              title: 'This is an title of the article',
              text: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam cupiditate veniam voluptatibus laudantium cum dolorem illo. Quos architecto deserunt saepe.',
            },
            {
              img: 'https://mdbcdn.b-cdn.net/img/Photos/Others/images/27.webp',
              title: 'This is an title of the article',
              text: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam cupiditate veniam voluptatibus laudantium cum dolorem illo. Quos architecto deserunt saepe.',
            },
            {
              img: 'https://mdbcdn.b-cdn.net/img/Photos/Others/images/25.webp',
              title: 'This is an title of the article',
              text: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam cupiditate veniam voluptatibus laudantium cum dolorem illo. Quos architecto deserunt saepe.',
            },
            {
              img: 'https://mdbcdn.b-cdn.net/img/Photos/Others/images/24.webp',
              title: 'This is an title of the article',
              text: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam cupiditate veniam voluptatibus laudantium cum dolorem illo. Quos architecto deserunt saepe.',
            },
            {
              img: 'https://mdbcdn.b-cdn.net/img/Photos/Others/images/31.webp',
              title: 'This is an title of the article',
              text: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam cupiditate veniam voluptatibus laudantium cum dolorem illo. Quos architecto deserunt saepe.',
            },
            {
              img: 'https://mdbcdn.b-cdn.net/img/Photos/Others/images/32.webp',
              title: 'This is an title of the article',
              text: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam cupiditate veniam voluptatibus laudantium cum dolorem illo. Quos architecto deserunt saepe.',
            },
          ];

          const getPostTemplate = (post) => {
            // returns the HTML template with post's title, image & text
            return `
              <div class="col-md-6 mb-4">
                <div class="bg-image hover-overlay ripple shadow-1-strong rounded mb-4" data-mdb-ripple-color="light">
                  <img src="${post.img}" class="w-100 lazy"/>
                  <a href="#!">
                    <div class="mask" style="background-color: rgba(251, 251, 251, 0.2);">
                    </div>
                  </a>
                </div>
                <h5>${post.title}</h5>
                <p>${post.text}</p>
                <a class="btn btn-info btn-rounded" href="#!" role="button">Read more</a>
              </div>
            `;
          };

          // posts - array of templates
          const posts = items.map((item) => getPostTemplate(item));

          const generateRow = (firstPost, secondPost) => {
            // Returns a div.row element with two columns generated based on arguments
            let el = document.createElement('div');
            el.classList.add('row');

            el.innerHTML = `
              ${firstPost}
              ${secondPost}
            `;
            return el;
          };

          // rows - array of rows with two posts each
          const rows = [];

          // iterates over posts and creates a row for every two of them
          for (let i = 0; i < posts.length - 1; i += 2) {
            rows.push(generateRow(posts[i], posts[i + 1]));
          }

          // renderedItems - number of items already rendered
          let renderedItems = 0;

          const renderItems = (index) => {
            // timeout simulates delay in loading items (f.e. API call)
            setTimeout(() => {
              // hide spinner
              spinner.style.display = 'none';

              postsContainer.appendChild(rows[index]);
            }, 1500);
          };

          const loadItems = () => {
            if (renderedItems < rows.length) {
              // show spinner
              postsContainer.appendChild(spinner);
              spinner.style.display = 'flex';

              renderItems(renderedItems);

              renderedItems++;

              // Removes event listener after all items have been loaded
              if (renderedItems === rows.length) {
                window.removeEventListener('complete.mdb.infiniteScroll', loadItems);
              }
            }
          };

          // load items when window is scrolled to the end
          window.addEventListener('complete.mdb.infiniteScroll', loadItems);

          // init infinite scroll
          new mdb.InfiniteScroll(window);
        
        
    

Infinite scroll - API


Usage

Via data attributes

        
            
      <div class="infinite-scroll" data-mdb-infinite-direction="..." data-mdb-infinite-container="...">
        Sample content
      </div>
    
        
    

Via JavaScript

        
            
      const infiniteScrollInstance = new mdb.InfiniteScroll(document.getElementById('element'), {
        infinite-direction: '...',
      });
    
        
    

Via jQuery

Note: By default, MDB does not include jQuery and you have to add it to the project on your own.

        
            
      $(document).ready(() => {
        $('.example-class').infiniteScroll(options);
      });
    
        
    

Options

Options can be passed via data attributes, JavaScript, or jQuery. For data attributes, append the option name to data-mdb-, as in data-mdb-infinite-direction="".

Name Type Default Description
infinite-direction String 'y' Defines an example scroll direction.

Methods

Name Description Example
dispose Removes an instance of the lazy element infiniteScrollInstance.toggle()
getInstance Static method which allows you to get the infinite scroll instance associated to a DOM element. InfiniteScroll.getInstance(infiniteScrollElement)
getOrCreateInstance Static method which returns the infinite scroll instance associated to a DOM element or create a new one in case it wasn't initialized. InfiniteScroll.getOrCreateInstance(infiniteScrollElement)
        
            
      const infiniteScrollElement = document.getElementById('element');
      const infiniteScrollInstance = new mdb.InfiniteScroll(infiniteScrollElement);
      infiniteScrollInstance.dispose();
    
        
    

Events

Name Description
complete.mdb.infiniteScroll This event fires immediately after scrolling to the end of the container.
        
            
      const infiniteScrollElement = document.getElementById('element');
      infiniteScrollElement.addEventListener('complete.mdb.infiniteScroll', (e) => {
        // do something...
      });
    
        
    

Import

MDB UI KIT also works with module bundlers. Use the following code to import this component:

        
            
      import { InfiniteScroll } from 'mdb-ui-kit';