UNPKG

@wareme/smooth-scrolling

Version:

High performance smooth scrolling for Dark applications

4 lines (2 loc) 14.6 kB
import{component as wh,useCallback as N,useEffect as S,useRef as k,useState as zh,createContext as Lh,useContext as Wh,stringify as Eh,detectIsEmpty as X}from"@dark-engine/core";import{useRafNexus as Hh}from"@wareme/raf-nexus";import{EventEmitter as a}from"@wareme/event-emitter";import{nisha as A}from"@wareme/utils";import{detectIsEmpty as f}from"@dark-engine/core";import{nisha as e}from"@wareme/utils";import{illegal as r}from"@dark-engine/core";var v=(h)=>r(h,"smooth-scrolling"),b=(h,i)=>{let s;return(...w)=>{clearTimeout(s),s=setTimeout(()=>h.apply(null,w),i)}},D=(h,i,s)=>{return Math.max(h,Math.min(i,s))};var o=(h,i,s)=>{return(1-s)*h+s*i},t=(h,i,s,w)=>{return o(h,i,1-Math.exp(-s*w))},C=(h,i)=>{return(h%i+i)%i};class V{advance(h){if(this.isRunning===!1)return;let i=!1;if(this.lerp){if(this.value=t(this.value,this.to,this.lerp*60,h),Math.round(this.value)===this.to)this.value=this.to,i=!0}else{this.currentTime+=h;let s=D(0,this.currentTime/this.duration,1);i=s>=1;let w=e(i,1,()=>this.easing(s));this.value=this.from+(this.to-this.from)*w}if(i)this.stop();if(f(this.onUpdate))return;this.onUpdate(this.value,i)}stop(){this.isRunning=!1}fromTo(h,i,{lerp:s,duration:w,easing:z,onStart:W,onUpdate:G}){if(this.from=h,this.value=h,this.to=i,this.lerp=s,this.duration=w,this.easing=z,this.currentTime=0,this.isRunning=!0,this.onUpdate=G,f(W))return;W()}}import{detectIsUndefined as u}from"@dark-engine/core";class j{constructor({wrapper:h,content:i,autoResize:s=!0,debounce:w=250}={}){if(this.wrapper=h,this.content=i,s){if(this.debouncedResize=b(this.resize,w),this.wrapper===window)window.addEventListener("resize",this.debouncedResize,!1);else this.wrapperResizeObserver=new ResizeObserver(this.debouncedResize),this.wrapperResizeObserver.observe(this.wrapper);this.contentResizeObserver=new ResizeObserver(this.debouncedResize),this.contentResizeObserver.observe(this.content)}this.resize()}destroy(){if(!u(this.wrapperResizeObserver))this.wrapperResizeObserver.disconnect();if(!u(this.contentResizeObserver))this.contentResizeObserver.disconnect();window.removeEventListener("resize",this.debouncedResize,!1)}resize=()=>{this.onWrapperResize(),this.onContentResize()};onWrapperResize=()=>{if(this.wrapper===window)this.width=window.innerWidth,this.height=window.innerHeight;else this.width=this.wrapper.clientWidth,this.height=this.wrapper.clientHeight};onContentResize=()=>{if(this.wrapper===window)this.scrollHeight=this.content.scrollHeight,this.scrollWidth=this.content.scrollWidth;else this.scrollHeight=this.wrapper.scrollHeight,this.scrollWidth=this.wrapper.scrollWidth};get limit(){return{x:this.scrollWidth-this.width,y:this.scrollHeight-this.height}}}import{EventEmitter as l}from"@wareme/event-emitter";var m=16.666666666666668;class F{constructor(h,{wheelMultiplier:i=1,touchMultiplier:s=1}){this.element=h,this.wheelMultiplier=i,this.touchMultiplier=s,this.touchStart={x:null,y:null},this.eventEmitter=new l,window.addEventListener("resize",this.onWindowResize,!1),this.onWindowResize(),this.elementListenerOptions={passive:!1},this.element.addEventListener("wheel",this.onWheel,this.elementListenerOptions),this.element.addEventListener("touchstart",this.onTouchStart,this.elementListenerOptions),this.element.addEventListener("touchmove",this.onTouchMove,this.elementListenerOptions),this.element.addEventListener("touchend",this.onTouchEnd,this.elementListenerOptions)}on=(h)=>this.eventEmitter.on(h);destroy=()=>{window.removeEventListener("resize",this.onWindowResize,!1),this.element.removeEventListener("wheel",this.onWheel,this.elementListenerOptions),this.element.removeEventListener("touchstart",this.onTouchStart,this.elementListenerOptions),this.element.removeEventListener("touchmove",this.onTouchMove,this.elementListenerOptions),this.element.removeEventListener("touchend",this.onTouchEnd,this.elementListenerOptions)};getTouchList=(h)=>{if(h.targetTouches)return h.targetTouches[0];return h};onTouchStart=(h)=>{let{clientX:i,clientY:s}=this.getTouchList(h);this.touchStart.x=i,this.touchStart.y=s,this.lastDelta={x:0,y:0},this.eventEmitter.emit({deltaX:0,deltaY:0,event:h})};onTouchMove=(h)=>{let{clientX:i,clientY:s}=this.getTouchList(h),w=-(i-this.touchStart.x)*this.touchMultiplier,z=-(s-this.touchStart.y)*this.touchMultiplier;this.touchStart.x=i,this.touchStart.y=s,this.lastDelta={x:w,y:z},this.eventEmitter.emit({deltaX:w,deltaY:z,event:h})};onTouchEnd=(h)=>this.eventEmitter.emit({deltaX:this.lastDelta.x,deltaY:this.lastDelta.y,event:h});getMultipliers=(h)=>{if(h===1)return[m,m];if(h===2)return[this.windowWidth,this.windowHeight];return[1,1]};onWheel=(h)=>{let{deltaX:i,deltaY:s,deltaMode:w}=h,[z,W]=this.getMultipliers(w);i*=z,s*=W,i*=this.wheelMultiplier,s*=this.wheelMultiplier,this.eventEmitter.emit({deltaX:i,deltaY:s,event:h})};onWindowResize=()=>{this.windowWidth=window.innerWidth,this.windowHeight=window.innerHeight}}import{detectIsEmpty as y,detectIsFunction as R,detectIsString as g}from"@dark-engine/core";class T{__isScrolling=!1;__isStopped=!1;__isLocked=!1;__preventNextNativeScrollEvent;__resetVelocityTimeout;time;userData;lastVelocity;velocity;direction;options;targetScroll;animatedScroll;constructor({wrapper:h=window,content:i=document.documentElement,eventsTarget:s=h,smoothWheel:w=!0,syncTouch:z=!1,syncTouchLerp:W=0.075,touchInertiaMultiplier:G=35,duration:Q,easing:O=(n)=>Math.min(1,1.001-Math.pow(2,-10*n)),lerp:J=0.1,infinite:K=!1,orientation:q="vertical",gestureOrientation:_="vertical",touchMultiplier:L=1,wheelMultiplier:B=1,autoResize:E=!0,prevent:$=!1}={}){if(!h||h===document.documentElement||h===document.body)h=window;this.options={wrapper:h,content:i,eventsTarget:s,smoothWheel:w,syncTouch:z,syncTouchLerp:W,touchInertiaMultiplier:G,duration:Q,easing:O,lerp:J,infinite:K,gestureOrientation:_,orientation:q,touchMultiplier:L,wheelMultiplier:B,autoResize:E,prevent:$},this.animate=new V,this.eventEmitter=new a,this.dimensions=new j({wrapper:h,content:i,autoResize:E}),this.updateClassName(),this.userData={},this.time=0,this.velocity=this.lastVelocity=0,this.isLocked=!1,this.isStopped=!1,this.isScrolling=!1,this.targetScroll=this.animatedScroll=this.actualScroll,this.options.wrapper.addEventListener("scroll",this.onNativeScroll,!1),this.options.wrapper.addEventListener("pointerdown",this.onPointerDown,!1),this.virtualScroll=new F(s,{touchMultiplier:L,wheelMultiplier:B}),this.virtualScroll.on(this.onVirtualScroll)}destroy(){this.options.wrapper.removeEventListener("scroll",this.onNativeScroll,!1),this.options.wrapper.removeEventListener("pointerdown",this.onPointerDown,!1),this.virtualScroll.destroy(),this.dimensions.destroy(),this.cleanUpClassName()}on(h){return this.eventEmitter.on(h)}off(h){return this.eventEmitter.off(h)}setScroll(h){if(this.isHorizontal)this.rootElement.scrollLeft=h;else this.rootElement.scrollTop=h}onPointerDown=(h)=>{if(h.button===1)this.reset()};onVirtualScroll=({deltaX:h,deltaY:i,event:s})=>{if(s.ctrlKey)return;let w=s.type.includes("touch"),z=s.type.includes("wheel");if(this.isTouching=s.type==="touchstart"||s.type==="touchmove",this.options.syncTouch&&w&&s.type==="touchstart"&&!this.isStopped&&!this.isLocked){this.reset();return}let G=h===0&&i===0,Q=this.options.gestureOrientation==="vertical"&&i===0||this.options.gestureOrientation==="horizontal"&&h===0;if(G||Q)return;let O=s.composedPath();O=O.slice(0,O.indexOf(this.rootElement));let J=this.options.prevent;if(Boolean(O.find((E)=>A(R(J),()=>J(E),J)||E.hasAttribute?.("data-odayaka-prevent")||w&&E.hasAttribute?.("data-odayaka-prevent-touch")||z&&E.hasAttribute?.("data-odayaka-prevent-wheel")||E.classList?.contains("odayaka")&&!E.classList?.contains("odayaka-stopped"))))return;if(this.isStopped||this.isLocked){s.preventDefault();return}if(!(this.options.syncTouch&&w||this.options.smoothWheel&&z)){this.isScrolling="native",this.animate.stop();return}s.preventDefault();let q=i;if(this.options.gestureOrientation==="both")if(Math.abs(i)>Math.abs(h))q=i;else q=h;else if(this.options.gestureOrientation==="horizontal")q=h;let _=w&&this.options.syncTouch,B=w&&s.type==="touchend"&&Math.abs(q)>5;if(B)q=this.velocity*this.options.touchInertiaMultiplier;this.scrollTo(this.targetScroll+q,{programmatic:!1,...A(_,{lerp:A(B,this.options.syncTouchLerp,1)},{lerp:this.options.lerp,duration:this.options.duration,easing:this.options.easing})})};resize(){this.dimensions.resize()}emit(){this.eventEmitter.emit(this)}onNativeScroll=()=>{if(clearTimeout(this.__resetVelocityTimeout),delete this.__resetVelocityTimeout,this.__preventNextNativeScrollEvent){delete this.__preventNextNativeScrollEvent;return}if(this.isScrolling===!1||this.isScrolling==="native"){let h=this.animatedScroll;if(this.animatedScroll=this.targetScroll=this.actualScroll,this.lastVelocity=this.velocity,this.velocity=this.animatedScroll-h,this.direction=Math.sign(this.animatedScroll-h),this.isScrolling="native",this.emit(),this.velocity!==0)this.__resetVelocityTimeout=setTimeout(()=>{this.lastVelocity=this.velocity,this.velocity=0,this.isScrolling=!1,this.emit()},400)}};reset(){this.isLocked=!1,this.isScrolling=!1,this.animatedScroll=this.targetScroll=this.actualScroll,this.lastVelocity=this.velocity=0,this.animate.stop()}start(){if(!this.isStopped)return;this.isStopped=!1,this.reset()}stop(){if(this.isStopped)return;this.isStopped=!0,this.animate.stop(),this.reset()}raf(h){let i=h-(this.time||h);this.time=h,this.animate.advance(i*0.001)}scrollTo(h,{offset:i=0,immediate:s=!1,lock:w=!1,duration:z=this.options.duration,easing:W=this.options.easing,lerp:G=this.options.lerp,onStart:Q,onComplete:O,force:J=!1,programmatic:K=!0,userData:q={}}={}){if((this.isStopped||this.isLocked)&&!J)return;if(g(h)&&["top","left","start"].includes(h))h=0;else if(g(h)&&["bottom","right","end"].includes(h))h=this.limit;else{let _;if(typeof h==="string")_=document.querySelector(h);else if(h instanceof HTMLElement&&h?.nodeType)_=h;if(_){if(this.options.wrapper!==window){let B=this.rootElement.getBoundingClientRect();i-=this.isHorizontal?B.left:B.top}let L=_.getBoundingClientRect();h=(this.isHorizontal?L.left:L.top)+this.animatedScroll}}if(typeof h!=="number")return;if(h+=i,h=Math.round(h),this.options.infinite){if(K)this.targetScroll=this.animatedScroll=this.scroll}else h=D(0,h,this.limit);if(h===this.targetScroll)return;if(this.userData=q,s){if(this.animatedScroll=this.targetScroll=h,this.setScroll(this.scroll),this.reset(),this.preventNextNativeScrollEvent(),this.emit(),!y(O)&&R(O))O(this);this.userData={};return}if(!K)this.targetScroll=h;this.animate.fromTo(this.animatedScroll,h,{duration:z,easing:W,lerp:G,onStart:()=>{if(w)this.isLocked=!0;this.isScrolling="smooth",Q?.(this)},onUpdate:(_,L)=>{if(this.isScrolling="smooth",this.lastVelocity=this.velocity,this.velocity=_-this.animatedScroll,this.direction=Math.sign(this.velocity),this.animatedScroll=_,this.setScroll(this.scroll),K)this.targetScroll=_;if(!L)this.emit();if(L){if(this.reset(),this.emit(),!y(O)&&R(O))O(this);this.userData={},this.preventNextNativeScrollEvent()}}})}preventNextNativeScrollEvent(){this.__preventNextNativeScrollEvent=!0,requestAnimationFrame(()=>{delete this.__preventNextNativeScrollEvent})}get rootElement(){if(this.options.wrapper===window)return document.documentElement;return this.options.wrapper}get limit(){return this.dimensions.limit[A(this.isHorizontal,"x","y")]}get isHorizontal(){return this.options.orientation==="horizontal"}get actualScroll(){return A(this.isHorizontal,this.rootElement.scrollLeft,this.rootElement.scrollTop)}get scroll(){return A(this.options.infinite,()=>C(this.animatedScroll,this.limit),this.animatedScroll)}get isScrolling(){return this.__isScrolling}set isScrolling(h){if(this.__isScrolling!==h)this.__isScrolling=h,this.updateClassName()}get isStopped(){return this.__isStopped}set isStopped(h){if(this.__isStopped!==h)this.__isStopped=h,this.updateClassName()}get isLocked(){return this.__isLocked}set isLocked(h){if(this.__isLocked!==h)this.__isLocked=h,this.updateClassName()}get isSmooth(){return this.isScrolling==="smooth"}get className(){let h="odayaka";if(this.isStopped)h+=" odayaka-stopped";if(this.isLocked)h+=" odayaka-locked";if(this.isScrolling)h+=" odayaka-scrolling";if(this.isScrolling==="smooth")h+=" odayaka-smooth";return h}updateClassName(){this.cleanUpClassName(),this.rootElement.className=`${this.rootElement.className} ${this.className}`.trim()}cleanUpClassName(){this.rootElement.className=this.rootElement.className.replace(/odayaka(-\w+)?/g,"").trim()}}import{useState as hh,useEffect as ih}from"@dark-engine/core";import{EventEmitter as sh}from"@wareme/event-emitter";class I{state;eventEmitter;constructor(h){this.state=h,this.eventEmitter=new sh}set=(h)=>{this.state=h,this.eventEmitter.emit(this.state)};on=(h)=>this.eventEmitter.on(h);get=()=>this.state}function p(h){let[i,s]=hh(h.get());return ih(()=>{return h.on((w)=>s(w))},[i]),i}import{jsxDEV as U}from"@dark-engine/core/jsx-dev-runtime";var P=Lh(null),x=new I({}),Oh=()=>{let h=Wh(P),i=p(x);if(X(h))return i;return h},_h=(h,i=[],s=0)=>{let{odayaka:w,addCallback:z,removeCallback:W}=Oh();return S(()=>{if(!h||!z||!W||w!==null)return;return z(h,s),h(w),()=>{W(h)}},[w,z,W,s,...i]),w},qh=wh(({rafNexus:h,wrapperRef:i,contentRef:s,slot:w,root:z=!1,options:W={},autoRaf:G=!0,rafPriority:Q=0,className:O,...J})=>{if(X(h)){let H=Hh();if(X(H))v("rafNexus prop was not provided and not found in context");h=H.rafNexus}let K=(H)=>{if(X(H))return k(null);return H},q=K(i),_=K(s),[L,B]=zh(null),E=k([]),$=N((H,Z)=>{let M=(d,c)=>d.priority-c.priority;E.current.push({callback:H,priority:Z}),E.current.sort(M)},[]),n=N((H)=>{let Z=(M)=>M.callback!==H;E.current=E.current.filter(Z)},[]);S(()=>{let H=new T({...W,...!z&&{wrapper:q.current,content:_.current}});return B(H),()=>{H.destroy(),B(null)}},[z,Eh(W)]),S(()=>{if(L===null||G===!1)return;return h.add((H)=>{if(L!==null)L.raf(H)},Q)},[L,G,Q]),S(()=>{if(z&&L!==null)return x.set({odayaka:L,addCallback:$,removeCallback:n}),()=>x.set({})},[z,L,$,n]);let Y=N((...H)=>{for(let Z=0,M=E.current.length;Z<M;Z++)E.current[Z].callback(...H)},[]);if(S(()=>{if(L!==null)L.on(Y);return()=>{if(L!==null)L.off(Y)}},[L,Y]),z)return U(P,{value:{odayaka:L,addCallback:$,removeCallback:n},children:w},void 0,!1,void 0,this);return U(P,{value:{odayaka:L,addCallback:$,removeCallback:n},children:U("div",{ref:q,className:O,...J,children:U("div",{ref:_,children:w},void 0,!1,void 0,this)},void 0,!1,void 0,this)},void 0,!1,void 0,this)});export{_h as useSmoothScrolling,qh as SmoothScrollingProvider}; //# debugId=3695F39C7832B7C964756E2164756E21