I can't get a spinning wheel to stop at specified sector index.
Once random index is created, it should find it's starting degree, and calculate the min max range so the wheel stops somewhere between in that sector's degree.
Can someone tell me what I'm missing here, because it does not work as expected.
WheelSpin component DEMO: https://playcode.io/2211878:
The part I'm talking about is a method spin()
:
function spin() {
if (isSpinning.value) return;
isSpinning.value = true;
const targetIndex = getRandomSectorIndex();
const extraSpins = Math.floor(4 + Math.random() * 4);
const angle = 360 / sectors.value.length;
const randomMinMax = (m: number, M: number) => Math.random() * (M - m) + m;
// Target sector angle
const min = targetIndex * angle;
const max = min + angle;
const targetRotation = randomMinMax(min, max);
const rotateTo = currentRotation.value + 360 * extraSpins + targetRotation;
console.log(`Should be: ${sectors.value[targetIndex].code} at index ${targetIndex} of ${targetRotation}deg`);
console.log('rotate to:', rotateTo);
isSpinning.value = false;
if (wheel.value) {
wheel.value.style.transition = 'transform 5s cubic-bezier(0.3, 0, 0.1, 1)';
wheel.value.style.transform = `rotate(${rotateTo}deg)`;
}
currentRotation.value = rotateTo;
setTimeout(() => {
isSpinning.value = false;
// Calculate stopped sector
const normalizedRotation = currentRotation.value % 360;
const sectorIndex = Math.floor(normalizedRotation / 30) % sectors.value.length;
// Reverse index because rotation is clockwise
const reversedIndex = sectors.value.length - 1 - sectorIndex;
done(reversedIndex);
}, 5000);
}
I tried to rotate using css approach but I'm not sure if it's a good idea. I have no idea either how to stop the wheel at specific sector.
I've tried to make sense of your code in order to help, but I couldn't wrap my head around its internal logic and how it was built (DOM + CSS). I can't really help you with that one.
However, I've created a separate snippet, from scratch, which might be of some help: https://codesandbox.io/p/sandbox/spinning-wheel-44t66w
The spin
property inside spin-wheel
store is the number of degrees by which the wheel is rotated relative to its non-rotating parts (.cirle-center
, .circle-arrow
and .circle-shadow
). And to change it, we use the following function:
import { gsap } from 'gsap'
// ...
spinWheel(newValue: number) {
this.spinning = true
const interpolator = gsap.utils.interpolate(this.spin, newValue)
gsap.to(
{},
{
duration: 3,
ease: 'power4.out',
onUpdate() {
useSpinWheel().spin = interpolator(this.ratio)
},
onComplete: this.endSpin
}
)
},
, inside the same store. The function performs a javascript based animation1, exposing onUpdate
and onEnd
functions. The onUpdate
updates spin
, which turns the wheel at the specified angle.
The rest are details (that's where the devil is :).
Now, how do we spin so we land on a particular item?
The arrow is conveniently located at the 0deg
position on the circle, which makes it easy to calculate the range of angles between which the arrow points at each item:
const min = 360 / sectors.length * sectorIndex
const max = 360 / sectors.length * (sectorIndex + 1)
If we set spin
to Math.random() * (max - min) + min
we're guaranteed to get a number between min
and max
, meaning we land on the item. For extra wheel spins, we can add multiples of 360
to the number.
This is all inside spinToItem
function in the store, performed when one clicks on a sector. For brevity, I renamed 360 / this.sectors.length
to this.angle
computed, because it was needed in a lot of places.
1 - you don't have to use gsap
, but I don't know of a better one, in terms of performance, ease of use and various other bells and whistles (ability to apply various easings, pause, reverse or sync animations, etc...).