javascriptcssvue.js

Stop a spinning wheel at specific angle degree?


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.


Solution

  • 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...).