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 ofhide
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);