quovadis
Version:
A small library to handle changes in scroll direction.
137 lines (123 loc) • 3.95 kB
JavaScript
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;