Created
October 9, 2024 16:17
-
-
Save Plaristote/36250fa999da634082d324c27b8cefdc to your computer and use it in GitHub Desktop.
Simple infinite scroll implementation for wordpress themes
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* The InfiniteScroll object fetches the HTML of the next page | |
* as soon as the "anchor" element (typically, the wordpress | |
* pagination element) becomes visible on screen. | |
* | |
* It then uses "postSelector" and "containerSelector" to extract | |
* posts from the page and append them to the current page. | |
* | |
* Example: | |
* | |
import InfiniteScroll from "./infinite-scroll.js"; | |
InfiniteScroll.factory({ | |
anchorSelector: "nav.pagination", | |
postSelector: ".post", | |
containerSelector: ".post-list", | |
loadingElement: document.getElementById("loading-element"), | |
endElement: document.getElementById("end-element") | |
}); | |
* | |
* We also assume the URL for the next page will be the current | |
* page's URL appended by `/page/${pageNumber}`. In case this | |
* assumption is not true in your case, you may overload the | |
* `nextUrl` getter, like this: | |
class MyInfiniteScroll extens InfiniteScroll { | |
get nextUrl() { | |
return `${window.location.href}?p=${this.currentPage + 1}`; | |
} | |
} | |
* | |
*/ | |
function toggleAfterContainerElement(element, container, value) { | |
if (value) { | |
container.parentNode.insertBefore(element, container.nextSibling); | |
} else { | |
element.remove(); | |
} | |
} | |
function isScrolledIntoView(el) { | |
const rect = el.getBoundingClientRect(); | |
const elemTop = rect.top; | |
const elemBottom = rect.bottom; | |
// Only completely visible elements return true: | |
return (elemTop >= 0) && (elemBottom <= window.innerHeight); | |
// Partially visible elements return true: | |
return elemTop < window.innerHeight && elemBottom >= 0; | |
} | |
function defaultLoadingElement() { | |
const element = document.createElement("div"); | |
element.classList.add("infinite-scroll-loading-hint"); | |
return element; | |
} | |
export default class InfiniteScroll { | |
static factory(options) { | |
return new Promise(function (resolve, reject) { | |
document.addEventListener("DOMContentLoaded", function() { | |
document.querySelector(options.anchorSelector) | |
? resolve(new InfiniteScroll(options)) | |
: reject("No anchor found for infinite scroll"); | |
}); | |
}); | |
} | |
constructor(options) { | |
this.options = options; | |
this.loadingElement = this.options.loadingElement || defaultLoadingElement(); | |
this.endElement = this.options.endElement; | |
this.reallow(); | |
this.loading = false; | |
this.hasMore = true; | |
this.delay = 10; | |
this.currentPage = 1; | |
window.addEventListener("scroll", this.onScroll.bind(this)); | |
document.querySelector(this.options.anchorSelector).style.opacity = 0; | |
} | |
get container() { | |
return document.querySelector(this.options.containerSelector); | |
} | |
get element() { | |
return document.querySelector(this.options.anchorSelector || "nav.pagination"); | |
} | |
get nextUrl() { | |
return `${window.location.href}page/${this.currentPage + 1}`; | |
} | |
set loading(value) { | |
this._loading = value; | |
toggleAfterContainerElement(this.loadingElement, this.container, value); | |
} | |
get loading() { | |
return this._loading; | |
} | |
set hasMore(value) { | |
this._hasMore = value; | |
if (this.endElement) { | |
toggleAfterContainerElement(this.endElement, this.container, !value); | |
} | |
} | |
get hasMore() { | |
return this._hasMore; | |
} | |
reallow() { | |
this.allow = true; | |
} | |
onScroll(event) { | |
if (this.hasMore && !this.loading && this.allow) { | |
this.allow = false; | |
setTimeout(this.reallow.bind(this), this.delay); | |
if (isScrolledIntoView(this.element)) this.fetchMore(); | |
} | |
} | |
fetchMore() { | |
this.loading = true; | |
return fetch(this.nextUrl) | |
.then(response => response.text()) | |
.then(html => { | |
const elements = this.newElementsFromHtml(html); | |
this.hasMore = elements.length > 0; | |
this.currentPage += 1; | |
this.appendNewElements(elements); | |
}).catch(error => { | |
console.error("Infinite scroll failed to fetch more:", error); | |
}).finally(() => { | |
this.loading = false; | |
}); | |
} | |
newElementsFromHtml(html) { | |
const container = document.createElement("div"); | |
container.innerHTML = html; | |
return Array.from( | |
container.querySelector(this.options.containerSelector) | |
.querySelectorAll(this.options.postSelector) | |
); | |
} | |
appendNewElements(elements) { | |
const container = this.container; | |
elements.forEach(element => { | |
container.appendChild(element); | |
}); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment