UNPKG

@adoratorio/hades

Version:

A smooth scrollbar based on Hermes, scroll down 'till hell

253 lines 9 kB
import Aion from '@adoratorio/aion'; import Hermes from '@adoratorio/hermes'; import { DIRECTION, } from "./declarations"; import Easings from "./easing"; class Hades { static EASING = Easings; static DIRECTION = DIRECTION; _amount = { x: 0, y: 0 }; _temp = { x: 0, y: 0 }; options; engine; manager; scrollHandler; frameHandler; timeline; prevDirection = { x: Hades.DIRECTION.INITIAL, y: Hades.DIRECTION.INITIAL }; prevAmount = { x: 0, y: 0 }; automaticScrolling = false; imediateScrolling = false; aionId = `hades-frame-${performance.now()}`; plugins = []; internalId = 0; amount = { x: 0, y: 0 }; velocity = { x: 0, y: 0 }; running = false; constructor(options) { const defaults = { root: document.body, easing: { mode: Easings.LINEAR, duration: 1000, }, autoplay: true, aion: null, globalMultiplier: 1, touchMultiplier: 1.5, smoothDirectionChange: false, threshold: { x: 0, y: 3, }, invert: false, precision: 4, }; this.options = { ...defaults, ...options }; this.timeline = { start: 0, duration: this.options.easing.duration, initial: { x: 0, y: 0 }, final: { x: 0, y: 0 }, current: { x: 0, y: 0 }, }; this.scrollHandler = (event) => this.scroll(event); this.frameHandler = (delta) => this.frame(delta); this.manager = new Hermes({ mode: Hermes.MODE.VIRTUAL, root: this.options.root, touchMultiplier: this.options.touchMultiplier, passive: false, }); if (this.options.autoplay) this.play(); if (this.options.aion === null || typeof this.options.aion === 'undefined') { this.engine = new Aion({}); } else { this.engine = this.options.aion; } this.engine.add(this.frameHandler, this.aionId); this.engine.start(); } frame(delta) { this.plugins.forEach((plugin) => plugin.preFrame && plugin.preFrame(this)); this.timeline.final.x = this._amount.x; this.timeline.final.y = this._amount.y; delta = Math.min(Math.max(delta, 0), this.options.easing.duration); let time = delta / this.timeline.duration; if (this.imediateScrolling) { time = 1; this.imediateScrolling = false; } time = this.options.easing.mode(time); this.timeline.current.x = this.timeline.initial.x + (time * (this.timeline.final.x - this.timeline.initial.x)); this.timeline.current.y = this.timeline.initial.y + (time * (this.timeline.final.y - this.timeline.initial.y)); const current = { x: this.timeline.current.x, y: this.timeline.current.y, }; this.amount = current; this.velocity = { x: (current.x - this.prevAmount.x) / delta, y: (current.y - this.prevAmount.y) / delta, }; this.prevAmount = this.amount; this.velocity.x = parseFloat(this.velocity.x.toFixed(this.options.precision)); this.velocity.y = parseFloat(this.velocity.y.toFixed(this.options.precision)); const currentXDirection = this.velocity.x === 0 ? (Hades.DIRECTION.INITIAL) : (this.velocity.x > 0 ? Hades.DIRECTION.DOWN : Hades.DIRECTION.UP); const currentYDirection = this.velocity.y === 0 ? (Hades.DIRECTION.INITIAL) : (this.velocity.y > 0 ? Hades.DIRECTION.DOWN : Hades.DIRECTION.UP); if (!this.options.smoothDirectionChange && !this.automaticScrolling) { if (currentXDirection !== this.prevDirection.x) this._amount.x = this.amount.x; if (currentYDirection !== this.prevDirection.y) this._amount.y = this.amount.y; } this.prevDirection.x = currentXDirection; this.prevDirection.y = currentYDirection; this.timeline.initial = this.timeline.current; this.plugins.forEach((plugin) => plugin.render && plugin.render(this)); } scroll(event) { let prevent = false; this.plugins.forEach((plugin) => { if (plugin.wheel) prevent = plugin.wheel(this, event); }); if (prevent) return; if (!this.running) return; if (Math.abs(event.delta.x) < this.options.threshold.x) event.delta.x = 0; if (Math.abs(event.delta.y) < this.options.threshold.y) event.delta.y = 0; if (this.automaticScrolling) { this.timeline.duration = this.options.easing.duration; this.amount = this.prevAmount; this.automaticScrolling = false; } this.plugins.forEach((plugin) => plugin.preScroll && plugin.preScroll(this, event)); event.delta.x = event.delta.x * this.options.globalMultiplier; event.delta.y = event.delta.y * this.options.globalMultiplier; this._temp.x = this._amount.x + (!this.options.invert ? event.delta.x : event.delta.y); this._temp.y = this._amount.y + (!this.options.invert ? event.delta.y : event.delta.x); this.plugins.forEach((plugin) => plugin.scroll && plugin.scroll(this, event)); this._amount.x = this._temp.x; this._amount.y = this._temp.y; } scrollTo(position, duration, prevent = false) { if (duration > 0) { this.automaticScrolling = true; this.timeline.duration = duration; } else { this.imediateScrolling = true; } if (!this.options.smoothDirectionChange) { this._amount.x = this.amount.x; this._amount.y = this.amount.y; } if (typeof position.x !== 'undefined') this._amount.x = position.x; if (typeof position.y !== 'undefined') this._amount.y = position.y; if (!prevent) { this.plugins.forEach((plugin) => plugin.scrollTo && plugin.scrollTo(this, position, duration)); } } registerPlugin(plugin, id) { let i = null; if (typeof id === 'undefined') { i = `hades-plugin-${this.internalId}`; this.internalId += 1; } else { i = id; } this.register(plugin, i); return i; } unregisterPlugin(id) { const foundIndex = this.plugins.findIndex((p) => p.id === id); if (foundIndex === -1) return false; const found = this.plugins[foundIndex]; if (typeof found?.destroy === 'function') found.destroy(); this.plugins.splice(foundIndex, 1); return true; } registerPlugins(plugins, ids) { const is = []; plugins.forEach((plugin, index) => { is.push(this.registerPlugin(plugin, ids[index])); }); return is; } getPlugin(name) { return this.plugins.find(plugin => plugin.name === name); } getRenderer() { const valid = ['VirtualRender', 'LenisRender', 'NativeRender']; return this.plugins.find((plugin => valid.includes(plugin.name))); } play() { this.running = true; this.manager.on(this.scrollHandler); } pause() { this.running = false; this.manager.off(); } destroy() { this.plugins.forEach((plugin) => plugin.destroy && plugin.destroy()); this.manager.destroy(); this.engine.remove(this.aionId); delete this.manager; delete this.engine; } get direction() { return this.prevDirection; } get root() { return this.options.root; } get internalAmount() { return this._amount; } get internalTemp() { return this._temp; } get still() { return this.direction.y === DIRECTION.INITIAL && this.direction.x === DIRECTION.INITIAL; } get easing() { return this.options.easing; } set easing(easing) { this.options.easing = easing; } set touchMultiplier(touchMultiplier) { this.options.touchMultiplier = touchMultiplier; } set smoothDirectionChange(smoothDirectionChange) { this.options.smoothDirectionChange = smoothDirectionChange; } set invert(invert) { this.options.invert = invert; } set internalAmount(values) { this._amount.x = values.x; this._amount.y = values.y; } set internalTemp(values) { this._temp.x = values.x; this._temp.y = values.y; } register(plugin, id) { if (typeof plugin.register === 'function') plugin.register(this); plugin.id = id; this.plugins.push(plugin); } } export default Hades; //# sourceMappingURL=index.js.map