wheel-duo
Version:
Animated dual-wheel component with customizable spin, sway, and callback support
3 lines (2 loc) • 6.45 kB
JavaScript
(function(o,l){typeof exports=="object"&&typeof module<"u"?l(exports):typeof define=="function"&&define.amd?define(["exports"],l):(o=typeof globalThis<"u"?globalThis:o||self,l(o.WheelDuo={}))})(this,function(o){Object.defineProperty(o,Symbol.toStringTag,{value:"Module"});var l=5e3,E=1500;function c(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 h(t,e){const i=n(t,e);if(i<=0)throw new Error(`${t} must be greater than 0`);return i}function d(t){const e=c("rootSelector",t.rootSelector),i=c("firstWheelSelector",t.firstWheelSelector),s=c("secondWheelSelector",t.secondWheelSelector),r=c("triggerSelector",t.triggerSelector),f=[n("targetAngles[0]",t.targetAngles[0]),n("targetAngles[1]",t.targetAngles[1])],a=h("rotations",t.rotations??6),m=h("duration",t.duration??5e3),u=n("overshootDeg",t.overshootDeg??15),g=h("returnDuration",t.returnDuration??750),p=n("swayOptions.amplitude",t.swayOptions?.amplitude??6),W=h("swayOptions.period",t.swayOptions?.period??1500),R=e.startsWith(".")?e.slice(1):e;return{rootSelector:e,firstWheelSelector:i,secondWheelSelector:s,triggerSelector:r,targetAngles:f,rotations:a,duration:m,overshootDeg:u,returnDuration:g,swayAmplitude:p,swayPeriod:W,callback:t.callback,rootClassName:R}}function w(){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 S=class{domRefs=null;swayAnimation=null;swayingElement=null;runtime=w();config;onFirstClick=()=>{this.runPhaseOne()};onSecondClick=()=>{this.runPhaseTwo()};constructor(t){this.config=d(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,r=i+this.config.rotations*360+s,f=r+this.config.overshootDeg,a=this.config.duration+this.config.returnDuration,m=this.config.duration/a,u=t.animate([{transform:`rotate(${i}deg)`,easing:"cubic-bezier(0.86,0,0.07,1)"},{offset:m,transform:`rotate(${f}deg)`,easing:"cubic-bezier(0.77,0,0.175,1)"},{transform:`rotate(${r}deg)`}],{duration:a,fill:"forwards"}),g=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:a,fill:"forwards",easing:"ease-in-out"});await Promise.all([u.finished,g.finished]),this.runtime.finalRotation.set(t,this.normalize(r))}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}};o.WheelDuo=S});
//# sourceMappingURL=index.umd.js.map