UNPKG

wheel-fortune

Version:

A lightweight, customizable spinning wheel component for web games and raffles

150 lines (149 loc) 5.94 kB
var m = class { rootElement = null; wheelElement = null; triggerElement = null; swayAnimation = null; swayingElement = null; finalRotation = /* @__PURE__ */ new WeakMap(); rotationCount; duration; overshootDeg; returnDuration; swayAmplitude; swayPeriod; rootClassName; spinStates; currentSpinIndex = 0; hasSpun = !1; warmedUp = !1; onClick = () => { this.runSpin(); }; constructor(t) { this.options = t, this.rotationCount = t.rotationCount ?? 6, this.duration = t.duration ?? 5e3, this.overshootDeg = t.overshootDeg ?? 15, this.returnDuration = t.returnDuration ?? 1350, this.swayAmplitude = t.swayOptions?.amplitude ?? 6, this.swayPeriod = t.swayOptions?.period ?? 1500, this.spinStates = [...t.spinStates ?? []]; const e = t.rootSelector.trim(); this.rootClassName = e.startsWith(".") ? e.slice(1) : e; } init() { const t = document.querySelector(this.options.rootSelector), e = t?.querySelector(this.options.wheelSelector), i = t?.querySelector(this.options.triggerSelector); !t || !e || !i || (this.triggerElement && this.triggerElement.removeEventListener("click", this.onClick), this.rootElement = t, this.wheelElement = e, this.triggerElement = i, this.warmUp(), this.triggerElement.addEventListener("click", this.onClick), this.startSway(this.wheelElement)); } destroy() { this.stopSway(), this.wheelElement && this.cancelAnimations(this.wheelElement), this.triggerElement && this.triggerElement.removeEventListener("click", this.onClick), this.finalRotation = /* @__PURE__ */ new WeakMap(); } reset() { if (!this.rootElement || !this.wheelElement || !this.triggerElement) { this.init(); return; } this.destroy(), this.wheelElement.style.transform = "", this.rootElement.classList.remove(`${this.rootClassName}--spinning`, `${this.rootClassName}--completed`, `${this.rootClassName}--first-done`, `${this.rootClassName}--final-done`), this.currentSpinIndex = 0, this.hasSpun = !1, this.warmedUp = !1, this.warmUp(), this.triggerElement.addEventListener("click", this.onClick), this.startSway(this.wheelElement); } warmUp() { if (this.warmedUp || !this.wheelElement) return; const t = this.wheelElement.animate([ { filter: "blur(0)" }, { filter: "blur(0.4px)", offset: 0.5 }, { filter: "blur(0)" } ], { duration: 64, fill: "forwards" }); this.wheelElement.getBoundingClientRect(), t.onfinish = () => { t.commitStyles?.(), t.cancel(); }, this.decodeImages(this.wheelElement), this.warmedUp = !0; } decodeImages(t) { t.querySelectorAll("img").forEach((e) => { e.decode?.().catch(() => { }); }); } async runSpin() { if (!this.rootElement || !this.wheelElement) return; const t = this.spinStates[this.currentSpinIndex]; t && (this.hasSpun = !0, this.rootElement.classList.remove(`${this.rootClassName}--completed`), this.rootElement.classList.add(`${this.rootClassName}--spinning`), this.stopSway(), await this.rotateWheelTo(this.wheelElement, t.targetAngle), this.rootElement.classList.remove(`${this.rootClassName}--spinning`), this.rootElement.classList.add(`${this.rootClassName}--completed`), this.currentSpinIndex === 0 && this.rootElement.classList.add(`${this.rootClassName}--first-done`), this.currentSpinIndex === this.spinStates.length - 1 && this.rootElement.classList.add(`${this.rootClassName}--final-done`), t.callback?.(), this.currentSpinIndex++); } async rotateWheelTo(t, e) { const i = this.getCurrentRotation(t), s = (this.normalize(e) - this.normalize(i) + 360) % 360, n = i + this.rotationCount * 360 + s, o = n + this.overshootDeg, r = this.duration + this.returnDuration, a = this.duration / r, l = t.animate([ { transform: `rotate(${i}deg)`, easing: "cubic-bezier(0.86,0,0.07,1)" }, { offset: a, transform: `rotate(${o}deg)`, easing: "cubic-bezier(0.77,0,0.175,1)" }, { transform: `rotate(${n}deg)` } ], { duration: r, fill: "forwards" }), h = t.animate([ { filter: "blur(0)" }, { offset: 0.1, filter: "blur(0.4px)" }, { offset: 0.25, filter: "blur(1px)" }, { offset: 0.6, filter: "blur(1px)" }, { offset: 0.9, filter: "blur(0.4px)" }, { offset: 1, filter: "blur(0)" } ], { duration: r, fill: "forwards", easing: "ease-in-out" }); await Promise.all([l.finished, h.finished]), this.finalRotation.set(t, this.normalize(n)); } startSway(t) { if (this.hasSpun) return; this.stopSway(), this.swayingElement = t; const e = this.finalRotation.get(t) ?? this.getCurrentRotation(t), i = this.normalize(e); this.swayAnimation = t.animate([{ transform: `rotate(${i - this.swayAmplitude}deg)` }, { transform: `rotate(${i + this.swayAmplitude}deg)` }], { duration: this.swayPeriod, direction: "alternate", iterations: Number.POSITIVE_INFINITY, easing: "ease-in-out", delay: -this.swayPeriod / 2 }); } stopSway() { if (!this.swayAnimation || !this.swayingElement) return; const t = this.swayingElement, e = getComputedStyle(t).transform; this.swayAnimation.commitStyles?.(), this.swayAnimation.cancel(), t.style.transform = e !== "none" ? e : "", 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(); }); } }; export { m as WheelFortune, m as default }; //# sourceMappingURL=index.es.js.map