UNPKG

quovadis

Version:

A small library to handle changes in scroll direction.

137 lines (123 loc) 3.95 kB
class Quovadis { constructor(opt = {}) { this.opt = Object.assign({}, this.defaults(), opt); this.context = this.opt.context; this.debug = this.opt.debug; this.prefix = this.opt.prefix; this.horizontal = this.opt.horizontal; this.historyLength = this.opt.historyLength; // Ticks to keep in history. this.historyMaxAge = this.opt.historyMaxAge; // History data time-to-live (ms). this.thresholdPixels = this.opt.thresholdPixels; // Ignore moves smaller than this. this.callback = this.opt.callback; this.event = this.opt.event; this.history = Array(this.historyLength); this.scrollingElement = this.context === document.documentElement ? window : this.context; this.isVer = !this.horizontal; return this; } defaults() { return { prefix: "scrolling", context: document.documentElement, debug: false, horizontal: false, historyLength: 32, historyMaxAge: 512, thresholdPixels: 64, callback: function () { let classes = document.documentElement.classList; for (var i = classes.length - 1; i >= 0; i--) { if (classes[i].startsWith(this.opt.prefix)) { classes.remove(classes[i]); } } classes.add(this.prefix + "-" + this.dir); }, event: true, }; } attach() { this.dir = this.isVer ? "down" : "right"; // 'up' or 'down' this.pivotTime = 0; this.pivot = this.isVer ? this.context.scrollTop : this.context.scrollLeft; this.callback(); this.handler = this.handler.bind(this); if (this.debug) console.log(this); return this.scrollingElement.addEventListener("scroll", this.handler); } detach() { return this.scrollingElement.removeEventListener("scroll", this.handler); } reattach(el) { this.context = el; this.history = Array(this.historyLength); this.scrollingElement = this.context === document.documentElement ? window : this.context; this.isVer = !this.horizontal; this.attach(); } tick() { let y = this.isVer ? this.context.scrollTop : this.context.scrollLeft; const t = this.e.timeStamp; const furthest = this.dir === "down" || this.dir === "right" ? Math.max : Math.min; // Apply bounds to handle rubber banding const yMax = this.isVer ? this.context.scrollHeight - this.context.clientHeight : this.context.scrollWidth - this.context.clientWidth; y = Math.max(0, y); y = Math.min(yMax, y); // Update history this.history.unshift({ y, t, }); this.history.pop(); // Are we continuing in the same direction? if (y === furthest(this.pivot, y)) { // Update "high-water mark" for current direction this.pivotTime = t; this.pivot = y; return; } // else we have backed off high-water mark // Apply max age to find current reference point const cutoffTime = t - this.historyMaxAge; if (cutoffTime > this.pivotTime) { this.pivot = y; for (let i = 0; i < this.historyLength; i += 1) { if (!this.history[i] || this.history[i].t < cutoffTime) break; this.pivot = furthest(this.pivot, this.history[i].y); } } // Have we exceeded threshold? if (Math.abs(y - this.pivot) > this.thresholdPixels) { this.pivot = y; this.pivotTime = t; if (this.isVer) { this.dir = this.dir === "down" ? "up" : "down"; } else { this.dir = this.dir === "right" ? "left" : "right"; } this.callback(); if (this.event === true) { this.dispatchEvent(); } } } handler(event) { this.e = event; window.requestAnimationFrame(this.tick.bind(this)); } dispatchEvent() { window.dispatchEvent( new CustomEvent("scrollDirectionChange", { detail: { direction: this.dir, }, }) ); } } export default Quovadis;