StuckObserver.js

/**
 * Extends IntersectionObserver to default callback to toggle a designated class when element is stuck
 * 
 * @extends IntersectionObserver
 * @param {Object} [options={}] - Configuration options
 * @param {Element} [options.root] - Same as IntersectionObserver
 * @param {number} [options.top=1] - Pixel inset into root to determine sticky state
 * @param {number} [options.right=-9999] - Pixel inset into root to determine sticky state, default essentially removes it as a boundary
 * @param {number} [options.bottom=-9999] - Pixel inset into root to determine sticky state, default essentially removes it as a boundary
 * @param {number} [options.left=-9999] - Pixel inset into root to determine sticky state, default essentially removes it as a boundary
 * @param {string} [options.class="stuck"] - Class to toggle, when stuck, class is added
 * @returns {StuckObserver} StuckObserver instance
 * @example
 * const observer = new StuckObserver(options={})
 * const el = createElement({style: {position: 'sticky', top: 0}})
 * observer.observe(el)
 */

export default class StuckObserver extends IntersectionObserver {
    
    constructor (options={}) {
        options = Object.assign({
            top: 1,
            right: -9999,
            bottom: -9999,
            left: -9999,
            class: 'stuck'
        }, options)
        const margin = ['top', 'right', 'bottom', 'left'].map(dir => options[dir] * -1 + "px").join(" ")
        super (entries => {
            entries.forEach(entry => {
                entry.target.classList.toggle(options.class, entry.intersectionRatio < 1);
                if (entry.target.stuckCallback) {
                    entry.target.stuckCallback(entry.intersectionRatio < 1)
                }
            })
        }, {
            root: options.root,
            rootMargin: margin,
            threshold: 1
        })
    }
    
    /**
     * Extends IntersectionObserver.observe to add a callback option that is called every time stuck status is changed
     * 
     * @param {HTMLElement} el - Element to observe
     * @param {Function} [callback] - Callback function that receives stuck status (true|false) as argument
     */
    observe(el, callback) {
        el.stuckCallback = callback
        return super.observe(el)
    }
}