wheel-duo
Version:
Animated dual-wheel component with customizable spin, sway, and callback support
3 lines (2 loc) • 6.23 kB
JavaScript
Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});function a(t,e){const i=e.trim();if(!i)throw new Error(`${t} must be a non-empty CSS selector`);return i}function n(t,e){if(!Number.isFinite(e))throw new Error(`${t} must be a finite number`);return e}function l(t,e){const i=n(t,e);if(i<=0)throw new Error(`${t} must be greater than 0`);return i}function w(t){const e=a("rootSelector",t.rootSelector),i=a("firstWheelSelector",t.firstWheelSelector),s=a("secondWheelSelector",t.secondWheelSelector),o=a("triggerSelector",t.triggerSelector),c=[n("targetAngles[0]",t.targetAngles[0]),n("targetAngles[1]",t.targetAngles[1])],r=l("rotations",t.rotations??6),h=l("duration",t.duration??5e3),f=n("overshootDeg",t.overshootDeg??15),m=l("returnDuration",t.returnDuration??750),g=n("swayOptions.amplitude",t.swayOptions?.amplitude??6),u=l("swayOptions.period",t.swayOptions?.period??1500),d=e.startsWith(".")?e.slice(1):e;return{rootSelector:e,firstWheelSelector:i,secondWheelSelector:s,triggerSelector:o,targetAngles:c,rotations:r,duration:h,overshootDeg:f,returnDuration:m,swayAmplitude:g,swayPeriod:u,callback:t.callback,rootClassName:d}}function S(){return{initialized:!1,phase:"idle",warmedElements:new WeakSet,finalRotation:new WeakMap}}function y(t){t.phase="idle",t.warmedElements=new WeakSet,t.finalRotation=new WeakMap}var p=class{domRefs=null;swayAnimation=null;swayingElement=null;runtime=S();config;onFirstClick=()=>{this.runPhaseOne()};onSecondClick=()=>{this.runPhaseTwo()};constructor(t){this.config=w(t)}init(){if(this.runtime.initialized)return;const t=this.getRequiredElement(document,this.config.rootSelector,"rootSelector"),e=this.getRequiredElement(t,this.config.firstWheelSelector,"firstWheelSelector"),i=this.getRequiredElement(t,this.config.secondWheelSelector,"secondWheelSelector"),s=this.getRequiredElement(t,this.config.triggerSelector,"triggerSelector");this.domRefs={root:t,firstWheel:e,secondWheel:i,trigger:s},this.runtime.initialized=!0,this.runtime.phase="idle",this.warmUp(e),this.warmUp(i),this.startSway(e),s.addEventListener("click",this.onFirstClick)}destroy(){!this.runtime.initialized||!this.domRefs||(this.stopSway(),this.cancelAnimations(this.domRefs.firstWheel),this.cancelAnimations(this.domRefs.secondWheel),this.domRefs.trigger.removeEventListener("click",this.onFirstClick),this.domRefs.trigger.removeEventListener("click",this.onSecondClick),this.runtime.initialized=!1,this.runtime.phase="idle")}reset(){const t=this.getDomRefs();this.destroy(),t.firstWheel.style.transform="",t.secondWheel.style.transform="",t.root.classList.remove(`${this.config.rootClassName}--state-one-active`,`${this.config.rootClassName}--state-one-complete`,`${this.config.rootClassName}--state-two-active`,`${this.config.rootClassName}--state-two-complete`),y(this.runtime),this.init()}warmUp(t){if(this.runtime.warmedElements.has(t))return;const e=t.animate([{filter:"blur(0)"},{filter:"blur(0.4px)",offset:.5},{filter:"blur(0)"}],{duration:64,fill:"forwards"});t.getBoundingClientRect(),e.onfinish=()=>{e.commitStyles?.(),e.cancel()},t.querySelectorAll("img").forEach(i=>{i.decode?.().catch(()=>{})}),this.runtime.warmedElements.add(t)}async runPhaseOne(){if(this.runtime.phase!=="idle")return;const t=this.getDomRefs(),[e]=this.config.targetAngles;this.runtime.phase="spinningFirst",t.root.classList.add(`${this.config.rootClassName}--state-one-active`),this.stopSway(),await this.rotateWheelTo(t.firstWheel,e),t.root.classList.replace(`${this.config.rootClassName}--state-one-active`,`${this.config.rootClassName}--state-one-complete`),this.startSway(t.secondWheel),t.trigger.removeEventListener("click",this.onFirstClick),t.trigger.addEventListener("click",this.onSecondClick,{once:!0}),this.runtime.phase="spinningSecond"}async runPhaseTwo(){if(this.runtime.phase!=="spinningSecond")return;const t=this.getDomRefs(),[,e]=this.config.targetAngles;t.root.classList.add(`${this.config.rootClassName}--state-two-active`),this.stopSway(),await this.rotateWheelTo(t.secondWheel,e),t.root.classList.replace(`${this.config.rootClassName}--state-two-active`,`${this.config.rootClassName}--state-two-complete`),this.runtime.phase="complete",this.config.callback?.()}async rotateWheelTo(t,e){const i=this.getCurrentRotation(t),s=(this.normalize(e)-this.normalize(i)+360)%360,o=i+this.config.rotations*360+s,c=o+this.config.overshootDeg,r=this.config.duration+this.config.returnDuration,h=this.config.duration/r,f=t.animate([{transform:`rotate(${i}deg)`,easing:"cubic-bezier(0.86,0,0.07,1)"},{offset:h,transform:`rotate(${c}deg)`,easing:"cubic-bezier(0.77,0,0.175,1)"},{transform:`rotate(${o}deg)`}],{duration:r,fill:"forwards"}),m=t.animate([{filter:"blur(0)"},{offset:.2,filter:"blur(1px)"},{offset:.4,filter:"blur(2px)"},{offset:.65,filter:"blur(1px)"},{offset:1,filter:"blur(0)"}],{duration:r,fill:"forwards",easing:"ease-in-out"});await Promise.all([f.finished,m.finished]),this.runtime.finalRotation.set(t,this.normalize(o))}startSway(t){this.stopSway(),this.swayingElement=t;const e=this.runtime.finalRotation.get(t)??this.getCurrentRotation(t);this.swayAnimation=t.animate([{transform:`rotate(${e-this.config.swayAmplitude}deg)`},{transform:`rotate(${e+this.config.swayAmplitude}deg)`}],{duration:this.config.swayPeriod,direction:"alternate",iterations:1/0,easing:"ease-in-out",delay:-this.config.swayPeriod/2})}stopSway(){if(!this.swayAnimation||!this.swayingElement)return;const t=getComputedStyle(this.swayingElement).transform;this.swayAnimation.commitStyles?.(),this.swayAnimation.cancel(),this.swayingElement.style.transform=t!=="none"?t:"",this.swayAnimation=null,this.swayingElement=null}normalize(t){return(t%360+360)%360}getCurrentRotation(t){const e=getComputedStyle(t).transform;if(!e||e==="none")return 0;const{a:i,b:s}=new DOMMatrixReadOnly(e);return Math.atan2(s,i)*180/Math.PI}cancelAnimations(t){t.getAnimations().forEach(e=>{e.cancel()})}getDomRefs(){if(!this.domRefs)throw new Error("WheelDuo is not initialized. Call init() before using this method.");return this.domRefs}getRequiredElement(t,e,i){const s=t.querySelector(e);if(!s)throw new Error(`WheelDuo init failed: element for "${i}" not found (${e})`);return s}};exports.WheelDuo=p;
//# sourceMappingURL=index.cjs.js.map