wheel-fortune
Version:
A lightweight, customizable spinning wheel component for web games and raffles
150 lines (149 loc) • 5.94 kB
JavaScript
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