<html>
  <head>
    <title>Vanilla JavaScript Infinite Scroll using WordPress REST API</title>
  </head>
  <body>
    <div class="container">
      <div class="row">
        <div class="col-md-8">
          <div class="posts"></div>
          <div class="load-more">
            <a href="#" class="btn btn-primary btn-lg btn-block btn-load-more">Load More</a>
          </div>
        </div>
      </div>
    </div>
  </body>
</html>
body {
  background-color: #eee;
}
.posts {
  margin: 40px 0;
}
.post-thumbnail {
  max-width: 100%;
  margin-bottom: 1rem;
}
.post-item {
  background-color: #fff;
  border-radius: 10px;
  box-shadow: 0 0 20px rgba(0, 0, 0, 0.1);
  margin-bottom: 40px;
  padding: 20px;
}

.load-more {
  margin-bottom: 40px;
}

.post-item a {
  color: #f60;
}

.post-item a:hover {
  color: #f90;
  text-decoration: none;
}
// This pen is a real example of how to build an Infinite Scroll
// in Vanilla JavaScript. I've used Fetch API, Intersection Observer API,
// and WordPress REST API to fetch posts.
// Feel free to fork, use and modify this code.
//
// Author: Cadu de Castro Alves
// Twitter: https://twitter.com/castroalves
// GitHub: https://github.com/castroalves
const WPInfiniteScroll = (() => {
  
  // Basic Configuration
  const config = {
    api: 'https://razoesparaacreditar.com/wp-json/wp/v2/posts',
    startPage: 0, // 0 for the first page, 1 for the second and so on...
    postsPerPage: 5 // Number of posts to load per page
  };
  
  // Private Properties
  let postsLoaded = false;
  let postsContent = document.querySelector('.posts');
  let btnLoadMore = document.querySelector('.btn-load-more');
  
  // Private Methods
  const loadContent = function() {
    
      // Starts with page = 1
      // Increase every time content is loaded
      ++config.startPage;
    
      // Basic query parameters to filter the API
      // Visit https://developer.wordpress.org/rest-api/reference/posts/#arguments
      // For information about other parameters
      const params = {
        _embed: true, // Required to fetch images, author, etc
        page: config.startPage, // Current page of the collection
        per_page: config.postsPerPage, // Maximum number of posts to be returned by the API
      }
    
      // console.log('_embed', params._embed);
      // console.log('per_page', params.per_page);
      // console.log('page', params.page);
    
      // Builds the API URL with params _embed, per_page, and page
      const getApiUrl = (url) => {
        let apiUrl = new URL(url);
        apiUrl.search = new URLSearchParams(params).toString();
        return apiUrl;
      };
    
      // Make a request to the REST API
      const loadPosts = async () => {
        const url = getApiUrl(config.api);
        const request = await fetch(url);
        const posts = await request.json();
        
        // Builds the HTML to show the posts
        const postsHtml = renderPostHtml(posts);
        
        // Adds the HTML into the posts div
        postsContent.innerHTML += postsHtml;
        
        // Required for the infinite scroll
        postsLoaded = true;
      };
    
      // Builds the HTML to show all posts
      const renderPostHtml = (posts) => {
        let postHtml = '';
        for(let post of posts) {
          postHtml += postTemplate(post);
        };
        return postHtml;
      };
    
      // HTML template for a post
      const postTemplate = (post) => {
        return `
            <div id="post-${post.id}" class="post-item">
              <img src="${post._embedded['wp:featuredmedia'][0].source_url}" class="post-thumbnail" />
              <h3 class="post-title"><a href="${post.link}?utm_source=codepen&utm_medium=link" target="_blank">${post.title.rendered}</a></h3>
              <p class="post-content">${post.excerpt.rendered}</p>
            </div>`;
      };
    
      loadPosts();
  };
  
  // Where the magic happens
  // Checks if IntersectionObserver is supported
  if ('IntersectionObserver' in window) {
    
    const loadMoreCallback = (entries, observer) => {
      entries.forEach((btn) => {
        if (btn.isIntersecting && postsLoaded === true) {
          postsLoaded = false;
          loadContent();
        }
      });
    };
    
    // Intersection Observer options
    const options = {
      threshold: 1.0 // Execute when button is 100% visible
    };
    
    let loadMoreObserver = new IntersectionObserver(loadMoreCallback, options);
    loadMoreObserver.observe(btnLoadMore);
  }
  
  // Public Properties and Methods
  return {
    init: loadContent
  };
  
})();

// Initialize Infinite Scroll
WPInfiniteScroll.init();

External CSS

  1. https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.1.3/css/bootstrap.min.css

External JavaScript

This Pen doesn't use any external JavaScript resources.