Thomas Vaeth
Thomas Vaeth

Load More

I make blog layouts a lot and wanted a nicer way of loading new posts without loading a new page. I also wanted to do that without using jQuery. So I wrote a script to load more posts for static site generators.


The Setup


<section>
  <div class="posts__container">
    <!-- Posts Here -->
  </div>
  <span class="posts__next">Load More Articles</span>
</section>

There is also the option to add some CSS. The script currently adds the class js-posts-loading to posts__next when the user clicks it. A spinner can be added to show the posts are loading. The class will be removed when the posts are finished loading. And there is a CSS keyframe animation named fade-up that has to be written and can be changed to something else.


The Script

There needs to be a variable called maxPages before the script for this to work. In this version I am just adding the variable at the end of the HTML, but I have also used it as data attributes on the posts container and returned the value in the settings object.


// Jekyll
var maxPages = parseInt('{{ paginator.total_pages }}');

// Ghost
var maxPages = parseInt('{{ pagination.pages }}');


const LoadMore = (() => {
  let s;

  return {
    settings() {
      return {
        container: document.querySelector('.posts__container'),
        next: document.querySelector('.posts__next'),
        class: 'js-posts-loading',
        animation: 'fade-up',
        currentPage: 1,
        pathname: window.location.pathname.replace(/#(.*)$/g, '').replace('//g', '/'),
        isLoading: false
      };
    },

    init() {
      s = this.settings();
      this.bindEvents();
    },

    bindEvents() {
      s.next.addEventListener('click', () => {
        this.requestPosts();
      });
    },

    requestPosts() {
      if (s.isLoading || s.currentPage === maxPages) {
        return;
      }

      s.isLoading = true;
      s.currentPage++;

      const request = new XMLHttpRequest();
      const nextPage = `${s.pathname}page/${s.currentPage}/`;

      request.open('GET', nextPage, true);
      request.send();
      
      request.onreadystatechange = () => {
        if (request.readyState < 4) {
          s.next.classList.add(s.class);
        } else if (request.readyState === 4 && request.status === 200) {
          const parse = document.createRange().createContextualFragment(request.response);
          const posts = parse.querySelectorAll('.posts__post');

          if (posts.length) {
            setTimeout(() => {
              [].forEach.call(posts, post => {
                post.classList.add(s.animation);
                s.container.appendChild(post);
              });

              s.next.classList.remove(s.class);

              if (s.currentPage === maxPages) {
                const child = document.querySelector('.posts__pagination');

                child.parentNode.removeChild(child);
              }
            }, 750);
          }
        } else {
          console.error(`${request.status} – ${request.statusText}`);
        }
      }

      s.isLoading = false;
    }
  };
})();

LoadMore.init();


Demos

Barber – Custom Blog for Jekyll and Ghost

Casper – Fork of Ghost Blog