@magic-spells/scroll-velocity
Version:
High-performance scroll velocity tracker with physics-based friction and CSS variable output for velocity-driven animations.
2 lines (1 loc) • 3.76 kB
JavaScript
!function(t,i){"object"==typeof exports&&"undefined"!=typeof module?i(exports):"function"==typeof define&&define.amd?define(["exports"],i):i((t="undefined"!=typeof globalThis?globalThis:t||self).ScrollVelocity={})}(this,function(t){"use strict";function i(t,i,e){return Math.max(i,Math.min(e,t))}t.ScrollVelocity=class ScrollVelocity{constructor(t={}){this.target=t.target||document.body,this.sampleMode=t.sampleMode||"delta",this.dampening="number"==typeof t.dampening?t.dampening:.35,this.friction="number"==typeof t.friction?t.friction:.92,this.attraction="number"==typeof t.attraction?t.attraction:.96,this.threshold="number"==typeof t.threshold?t.threshold:.02,this.maxVelocity="number"==typeof t.maxVelocity?t.maxVelocity:200,this.writeCSSVariables=!1!==t.writeCSSVariables,this.respectReducedMotion=!1!==t.respectReducedMotion,this._isRunning=!1,this._rafId=0,this._velocity=0,this._lastScrollY=0,this._lastTime=0,this._boundOnScroll=this._onScroll.bind(this),this._boundOnRaf=this._onRaf.bind(this)}start(){this._isRunning||(this._isRunning=!0,this._lastScrollY=window.scrollY||window.pageYOffset||0,this._lastTime=performance.now(),window.addEventListener("scroll",this._boundOnScroll,{passive:!0}),this._rafId=requestAnimationFrame(this._boundOnRaf))}stop(){this._isRunning&&(this._isRunning=!1,window.removeEventListener("scroll",this._boundOnScroll),this._rafId&&cancelAnimationFrame(this._rafId),this._rafId=0,this._velocity=0,this._writeCSS(0))}setOptions(t={}){"target"in t&&t.target&&(this.target=t.target),"sampleMode"in t&&(this.sampleMode=t.sampleMode||"delta"),"dampening"in t&&"number"==typeof t.dampening&&(this.dampening=t.dampening),"friction"in t&&"number"==typeof t.friction&&(this.friction=t.friction),"attraction"in t&&"number"==typeof t.attraction&&(this.attraction=t.attraction),"threshold"in t&&"number"==typeof t.threshold&&(this.threshold=t.threshold),"maxVelocity"in t&&"number"==typeof t.maxVelocity&&(this.maxVelocity=t.maxVelocity),"writeCSSVariables"in t&&(this.writeCSSVariables=!!t.writeCSSVariables),"respectReducedMotion"in t&&(this.respectReducedMotion=!!t.respectReducedMotion)}getVelocity(){return this._velocity}getNormalizedVelocity(){return this.maxVelocity<=0?0:i(this._velocity/this.maxVelocity,-1,1)}_onScroll(){if(this.respectReducedMotion&&window.matchMedia&&window.matchMedia("(prefers-reduced-motion: reduce)").matches)return this._velocity=0,this._writeCSS(0),void(this._rafId||(this._rafId=requestAnimationFrame(this._boundOnRaf)));const t=window.scrollY||window.pageYOffset||0,e=performance.now(),s=t-this._lastScrollY,o=e-this._lastTime||16.7;let n;this._lastScrollY=t,this._lastTime=e,"time"===this.sampleMode?(n=o>0?s/o:0,n*=12):n=s;const r=this._velocity+n;this._velocity+=(r-this._velocity)*this.dampening,this._velocity=i(this._velocity,-this.maxVelocity,this.maxVelocity),this._rafId||(this._rafId=requestAnimationFrame(this._boundOnRaf))}_onRaf(){this._rafId=0,this.respectReducedMotion&&window.matchMedia&&window.matchMedia("(prefers-reduced-motion: reduce)").matches||(this._velocity*=this.friction,this._velocity*=this.attraction,Math.abs(this._velocity)<this.threshold&&(this._velocity=0)),this._writeCSS(this.getNormalizedVelocity()),this._isRunning&&Math.abs(this._velocity)>0&&(this._rafId=requestAnimationFrame(this._boundOnRaf))}_writeCSS(t){if(!this.writeCSSVariables||!this.target||!this.target.style)return;const i=Math.abs(t),e=Math.min(1,1.35*Math.pow(i,.7));this.target.style.setProperty("--scroll-velocity",String(t.toFixed(4))),this.target.style.setProperty("--scroll-velocity-abs",String(i.toFixed(4))),this.target.style.setProperty("--scroll-velocity-pow",String(e.toFixed(4))),this.target.style.setProperty("--scroll-velocity-raw",String(this._velocity.toFixed(2)))}}});