UNPKG

wheel-duo

Version:

Animated dual-wheel component with customizable spin, sway, and callback support

190 lines (189 loc) 7.63 kB
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: /* @__PURE__ */ new WeakSet(), finalRotation: /* @__PURE__ */ new WeakMap() }; } function y(t) { t.phase = "idle", t.warmedElements = /* @__PURE__ */ new WeakSet(), t.finalRotation = /* @__PURE__ */ 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: 0.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: 0.2, filter: "blur(1px)" }, { offset: 0.4, filter: "blur(2px)" }, { offset: 0.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; } }; export { p as WheelDuo }; //# sourceMappingURL=index.es.js.map