Infinite Scrolling Proof of Concept

Hi Themeco family!

Alex Rohmann here. I hope everyone’s doing well. It’s been a while since I’ve used Pro, but I’m working on a site for a friend and wanted to dive back in. Really impressed with everything in more recent versions.

For this project I wanted to take a stab at implementing an infinite scrolling feature. There are some limitations, but it worked well enough that I felt like it might be worth sharing. Doesn’t work well with dropdowns/modals because the toggleable IDs are not unique across new elements. I haven’t tested this extensively or used it on a live site yet, but the mechanics seem to be coming together.

Setup
This requires setting up some custom attributes and specific markup.

  • Add [infinite-scrolling="container"] on the parent element of your posts.
  • Add [infinite-scrolling="pagination"] on your Post Pagination element. This will get hidden automatically, but still used to detect the next posts.
  • Add [infinite-scrolling="message"] and class of hide on an element that will say “No more posts” to indicate when you’ve reached the end.
  • Add [infinite-scrolling="loaded"] on an element you want to display when posts are loading.
  • Add the code below to your global (or archive layout) JS.
(function({ rivet: { attach, util: { intersect }} }){
  let infiniteScrollingRequests = 0; // allows access to _infiniteScrolling via the URL on subsequent renders if needed
  function updatePosts(next) {
    const url = new URL(next);
    url.searchParams.set('_infiniteScrolling', ++infiniteScrollingRequests);

    const mainContainer = document.querySelector('[infinite-scrolling="container"]');
    if (!mainContainer) return Promise.reject('Missing container');
    
    return new Promise((resolve,reject) => {
      fetch(url.toString(), { includeCredentials: true })
        .then(result => result.text())
        .then(html => {
          // Parse the HTML response in to a working document
        	const parser = new DOMParser();
        	const doc = parser.parseFromString(html,'text/html');
          // Find the posts container of the response and insert all of its children into the original container
        	const container = doc.querySelector('[infinite-scrolling="container"]')
        	Array.from(container.children).forEach(child => {
              mainContainer.append(child);
					});
          // return the new pagination element so it can be re-inserted at the exact position as the previous one
        	resolve(doc.querySelector('[infinite-scrolling="pagination"]'));
      	})
      	.catch(reject);
    });
  }
  
  attach('[infinite-scrolling="pagination"]', function(el) {
    el.style.opacity = 0;
    const next = el.querySelector('a.next')?.getAttribute('href');

    if (!next) { // show the "no results" message
    	el.remove();
    	document.querySelector('[infinite-scrolling="message"]')?.classList.remove('hide')
    	return;
    }

    const loader = document.querySelector('[infinite-scrolling="loader"]');

    return intersect(el, ({ isIntersecting }) => {
      if (isIntersecting) {
        loader?.classList.remove('hide'); // show the loader
    		updatePosts(next)
    			.then(nextPagination => el.after(nextPagination)) // replace the pagination with the version from the next page
    			.finally(() => {
            loader?.classList.add('hide');  // hide the loader
            el.remove();
          });
      }
    })
  });
})(window.csGlobal);
9 Likes

Great for Kyle and I to hear from you. Hope you have been well friend. Thanks for the kind words. You were a great inspiration for the External Rest API and we’ll definitely keep this in mind when we tackle Infinite Scroll in Cornerstone. Have a great day!

1 Like

uh, nice, thanks for sharing! :heart:

Thanks Alex! This is great.