Search code examples
javascripthtmlcanvas

Troubles with wheel of fortune


I use the wheel of fortune by Roko C. Buljan from here: how to draw a wheel of fortune?

This is my first experience with JavaScipt and canvas, and I have two questions.

First:
How can I get the value after spinning? I tried using setTimeout for this, but I just don't understand when it stops.

Second:
I want to put long sentences in labels, but they go out of bounds.

I've tried using a flexible font, but that doesn't really work and isn't exactly what I need. I need the text inside each block to move to a new line and reduce the font if it goes out of bounds.

Can all of this be implemented in this code, or would I need to write everything from scratch?

const sectors = [
  {color:"#f82", label:"Stack"},
  {color:"#0bf", label:"10"},
  {color:"#fb0", label:"200"},
  {color:"#0fb", label:"50"},
  {color:"#b0f", label:"100"},
  {color:"#f0b", label:"5"},
  {color:"#bf0", label:"500"},
];

const rand = (m, M) => Math.random() * (M - m) + m;
const tot = sectors.length;
const EL_spin = document.querySelector("#spin");
const ctx = document.querySelector("#wheel").getContext('2d');
const dia = ctx.canvas.width;
const rad = dia / 2;
const PI = Math.PI;
const TAU = 2 * PI;
const arc = TAU / sectors.length;

const friction = 0.991; // 0.995=soft, 0.99=mid, 0.98=hard
let angVel = 0; // Angular velocity
let ang = 0; // Angle in radians

const getIndex = () => Math.floor(tot - ang / TAU * tot) % tot;

function drawSector(sector, i) {
  const ang = arc * i;
  ctx.save();
  // COLOR
  ctx.beginPath();
  ctx.fillStyle = sector.color;
  ctx.moveTo(rad, rad);
  ctx.arc(rad, rad, rad, ang, ang + arc);
  ctx.lineTo(rad, rad);
  ctx.fill();
  // TEXT
  ctx.translate(rad, rad);
  ctx.rotate(ang + arc / 2);
  ctx.textAlign = "right";
  ctx.fillStyle = "#fff";
  ctx.font = "bold 30px sans-serif";
  ctx.fillText(sector.label, rad - 10, 10);
  //
  ctx.restore();
};

function rotate() {
  const sector = sectors[getIndex()];
  ctx.canvas.style.transform = `rotate(${ang - PI / 2}rad)`;
  EL_spin.textContent = !angVel ? "SPIN" : sector.label;
  EL_spin.style.background = sector.color;
}

function frame() {
  if (!angVel) return;
  angVel *= friction; // Decrement velocity by friction
  if (angVel < 0.002) angVel = 0; // Bring to stop
  ang += angVel; // Update angle
  ang %= TAU; // Normalize angle
  rotate();
}

function engine() {
  frame();
  requestAnimationFrame(engine)
}

// INIT
sectors.forEach(drawSector);
rotate(); // Initial rotation
engine(); // Start engine
EL_spin.addEventListener("click", () => {
  if (!angVel) angVel = rand(0.25, 0.35);
});
#wheelOfFortune {
  display: inline-block;
  position: relative;
  overflow: hidden;
}

#wheel {
  display: block;
}

#spin {
  font: 1.5em/0 sans-serif;
  user-select: none;
  cursor: pointer;
  display: flex;
  justify-content: center;
  align-items: center;
  position: absolute;
  inset: 35%;
  background: #fff;
  color: #fff;
  box-shadow: 0 0 0 8px currentColor, 0 0px 15px 5px rgba(0, 0, 0, 0.6);
  border-radius: 50%;
  transition: 0.8s;
}

#spin::after {
  content: "";
  position: absolute;
  top: -17px;
  border: 10px solid transparent;
  border-bottom-color: currentColor;
  border-top: none;
}
<div id="wheelOfFortune">
  <canvas id="wheel" width="300" height="300"></canvas>
  <div id="spin">SPIN</div>
</div>


Solution

  • Note

    I'll only answer your first question, as your second question of resizing font on a canvas is a big topic on its own and deserves its own question (though I'm sure it has been answered on StackOverflow before).

    How to get the value after spinning

    First: How can I get the value after spinning? I tried using setTimeout for this, but I just don't understand when it stops.

    As I suggested in the comment, all you need to do is check if during the last frame update the wheel stopped spinning.

    I've included the snippet below that accomplishes this, but note that I only changed the following:

    1. At the start of the frame function, I'm now setting an isSpinning variable.
    2. At the end of the same frame function, I'm using this isSpinning variable and the current velocity to determine if the wheel has just stopped. If it has, I'm calling a new finishedSpinning function.
    3. Within this new finishedSpinning function, I'm using the existing sectors array and getIndex to find the active sector. As an example, I'm then throwing an alert with the label, but you could replace this by whatever you need.

    I hope this helps! 😉

    const sectors = [
      {color:"#f82", label:"Stack"},
      {color:"#0bf", label:"10"},
      {color:"#fb0", label:"200"},
      {color:"#0fb", label:"50"},
      {color:"#b0f", label:"100"},
      {color:"#f0b", label:"5"},
      {color:"#bf0", label:"500"},
    ];
    
    const rand = (m, M) => Math.random() * (M - m) + m;
    const tot = sectors.length;
    const EL_spin = document.querySelector("#spin");
    const ctx = document.querySelector("#wheel").getContext('2d');
    const dia = ctx.canvas.width;
    const rad = dia / 2;
    const PI = Math.PI;
    const TAU = 2 * PI;
    const arc = TAU / sectors.length;
    
    const friction = 0.991; // 0.995=soft, 0.99=mid, 0.98=hard
    let angVel = 0; // Angular velocity
    let ang = 0; // Angle in radians
    
    const getIndex = () => Math.floor(tot - ang / TAU * tot) % tot;
    
    function drawSector(sector, i) {
      const ang = arc * i;
      ctx.save();
      // COLOR
      ctx.beginPath();
      ctx.fillStyle = sector.color;
      ctx.moveTo(rad, rad);
      ctx.arc(rad, rad, rad, ang, ang + arc);
      ctx.lineTo(rad, rad);
      ctx.fill();
      // TEXT
      ctx.translate(rad, rad);
      ctx.rotate(ang + arc / 2);
      ctx.textAlign = "right";
      ctx.fillStyle = "#fff";
      ctx.font = "bold 30px sans-serif";
      ctx.fillText(sector.label, rad - 10, 10);
      //
      ctx.restore();
    };
    
    function rotate() {
      const sector = sectors[getIndex()];
      ctx.canvas.style.transform = `rotate(${ang - PI / 2}rad)`;
      EL_spin.textContent = !angVel ? "SPIN" : sector.label;
      EL_spin.style.background = sector.color;
    }
    
    function finishedSpinning() { // Called when the wheel stops spinning
      const sector = sectors[getIndex()];
      alert(sector.label);
    }
    
    function frame() {
      if (!angVel) return;
      const isSpinning = angVel > 0; // Check if the wheel is currently spinning
      angVel *= friction; // Decrement velocity by friction
      if (angVel < 0.002) angVel = 0; // Bring to stop
      ang += angVel; // Update angle
      ang %= TAU; // Normalize angle
      rotate();
      
      if (isSpinning && angVel === 0) { // If the wheel was spinning, but isn't anymore, it has just stopped
        finishedSpinning();
      }
    }
    
    function engine() {
      frame();
      requestAnimationFrame(engine)
    }
    
    // INIT
    sectors.forEach(drawSector);
    rotate(); // Initial rotation
    engine(); // Start engine
    EL_spin.addEventListener("click", () => {
      if (!angVel) angVel = rand(0.25, 0.35);
    });
        #wheelOfFortune {
      display: inline-block;
      position: relative;
      overflow: hidden;
    }
    
    #wheel {
      display: block;
    }
    
    #spin {
      font: 1.5em/0 sans-serif;
      user-select: none;
      cursor: pointer;
      display: flex;
      justify-content: center;
      align-items: center;
      position: absolute;
      top: 50%;
      left: 50%;
      width: 30%;
      height: 30%;
      margin: -15%;
      background: #fff;
      color: #fff;
      box-shadow: 0 0 0 8px currentColor, 0 0px 15px 5px rgba(0, 0, 0, 0.6);
      border-radius: 50%;
      transition: 0.8s;
    }
    
    #spin::after {
      content: "";
      position: absolute;
      top: -17px;
      border: 10px solid transparent;
      border-bottom-color: currentColor;
      border-top: none;
    }
    <div id="wheelOfFortune">
      <canvas id="wheel" width="300" height="300"></canvas>
      <div id="spin">SPIN</div>
    </div>