Post

Planning to write own infinite-scroll.js

Harianto van Insulinde

The current state of infinite-scroll.js from the default Casper theme is buggy. I’m going to improve the script and make it work.

Found out it’s my own fault for editing index.hbs file. I didn’t expect you need to add extra argument page to load correct offset from a list. Get Posts exclude tags

js
;(() => {
    const nextElement = document.querySelector('link[rel=next]'), // next link element
        feedElement = document.querySelector('.post-feed') // post feed element
    if (!nextElement || !feedElement) return

    const state = {
        activeZoneHeight: 300,
        ticking: false,
        lastScrollY: window.scrollY,
        lastWindowHeight: window.innerHeight,
        lastDocumentHeight: document.documentElement.scrollHeight,
        href: nextElement.href
    }

    const app = {
        loading: false,
        onPageLoad(doc) {
            const nextElement = doc.querySelector('link[rel=next]')
            state.href = nextElement && nextElement.href
            !state.href && EventListeners.onDestroy()

            const postElements = doc.querySelectorAll('.post-card')
            Array.from(postElements).forEach(item => {
                console.log(`# ${item.querySelector('.post-card-title').innerHTML}`)
                // document.importNode is important, without it the item's owner
                // document will be different which can break resizing of
                // `object-fit: cover` images in Safari
                // feedElement.appendChild(body.importNode(item, true))
                feedElement.appendChild(item)
            })
        },
        onUpdate() {
            const {
                    lastScrollY,
                    lastWindowHeight,
                    lastDocumentHeight,
                    activeZoneHeight,
                    href
                } = state,
                { loading } = this

            if (loading || !href) return

            // return if not scroll to the bottom
            if (
                lastScrollY + lastWindowHeight <=
                lastDocumentHeight - activeZoneHeight
            ) {
                state.ticking = false
                return
            }

            this.loading = true
            console.log('---- onUpdate: START', { href })
            fetch(href)
                .then(r => r.text())
                .then(text => new DOMParser().parseFromString(text, 'text/html'))
                .then(this.onPageLoad)
                .then(() => {
                    console.log('---- onUpdate: END')
                    // sync status
                    this.loading = false
                    state.lastDocumentHeight = document.documentElement.scrollHeight
                    state.ticking = false
                })
        }
    }

    const requestTick = () => {
            state.ticking || requestAnimationFrame(app.onUpdate.bind(app))
            state.ticking = true
        },
        onScroll = event => {
            state.lastScrollY = window.scrollY
            requestTick()
        },
        onResize = event => {
            state.lastWindowHeight = window.innerHeight
            state.lastDocumentHeight = document.documentElement.scrollHeight
            requestTick()
        }

    const factoryListener = list => {
        let isCreated = false
        return {
            onCreate() {
                if (!isCreated) {
                    list.forEach(element => addEventListener(...element))
                    isCreated = true
                }
            },
            onDestroy() {
                if (isCreated) {
                    list.forEach(element => removeEventListener(...element))
                    isCreated = false
                }
            }
        }
    }

    const eventListeners = [
            ['scroll', onScroll, { passive: true }],
            ['resize', onResize]
        ],
        EventListeners = factoryListener(eventListeners)
    EventListeners.onCreate()
})()

File: infinite-scroll.js

I made little bloat for factoryListener function. It’s an idea to have multiple listeners in a list to destroy later if you worry about memory management or element manipulation. Some best practises are when you create an addEventListner you must have removeEventListener too clean it up.