skip to Main Content

I built a roulette using canvas, since I’m new at it I’m not being able to get the color I need.
The roulette has an arrow at its top, I want to log the color that are below the arrow.

Here should be purple but it is yellow:

enter image description here

How can I do that?

I believe that there is a way to use coordinates to get this info, but I couldn’t do it so far.

If it is necessary there is no problem adding some reference arrow at the same position of the golden one.

const canvas = document.getElementById("roulette-canvas");
const ctx = canvas.getContext("2d");
const spinButton = document.getElementById("spin-button");
const segments = 5;
const segmentSize = 360 / segments;
const colors = ["red", "yellow", "blue", "orange", "purple"];
const spinTime = 4500; // Spin time in milliseconds
const decelerationRate = 0.97; // Rate at which velocity decreases
let degree = 0;
let rotation = 0;
let velocity = 0;
let spinning = false;

function drawRoulette() {
  ctx.beginPath();
  ctx.arc(canvas.width / 2, canvas.height / 2, 353, 0, 2 * Math.PI);
  ctx.strokeStyle = "#D3A466"; // Golden color
  ctx.lineWidth = 12;
  ctx.stroke();

  // Draw the segments
  for (let i = 0; i < segments; i++) {
    ctx.fillStyle = colors[i];
    ctx.beginPath();
    ctx.moveTo(canvas.width / 2, canvas.height / 2);
    ctx.arc(
      canvas.width / 2,
      canvas.height / 2,
      350,
      (segmentSize * i - 90) * Math.PI / 180,
      (segmentSize * (i + 1) - 90) * Math.PI / 180
    );
    ctx.lineTo(canvas.width / 2, canvas.height / 2);
    ctx.fill();
  }
}

function spin() {
  if (spinning) return;

  spinning = true;

  // Random number between 1 and 360
  degree = Math.floor(Math.random() * 360) + 1;
  velocity = 10; // Initial velocity

  let start = null;
  let stopAnimation = false;

  function animation(timestamp) {
    if (!start) start = timestamp;
    let progress = timestamp - start;
    rotation += (velocity * Math.PI) / 180 / 20; // Increase rotation based on velocity
    velocity *= decelerationRate; // Decrease velocity based on deceleration rate

    // Stop the animation when velocity becomes too low
    if (velocity < 0.1) {
      velocity = 0;
      spinning = false;
    }

    if (!stopAnimation && progress >= spinTime - 1500) {
      // Start gradually slowing down the animation one second before it stops
      velocity *= decelerationRate;
      stopAnimation = true;
    }

    if (progress >= spinTime - 2000) velocity = velocity - 2;

    ctx.clearRect(0, 0, canvas.width, canvas.height);
    ctx.translate(canvas.width / 2, canvas.height / 2);
    ctx.rotate(rotation);
    ctx.translate(-canvas.width / 2, -canvas.height / 2);
    drawRoulette();

    if (progress < spinTime) {
      requestAnimationFrame(animation);
    } else {
      // Calculate the segment that the roulette stopped at
      let stopDegree = (degree - rotation / Math.PI * 180) % 360;
      let segmentNumber = 0;

      for (let i = 0; i < segments; i++) {
        if (
          stopDegree >= segmentSize * i &&
          stopDegree < segmentSize * (i + 1)
        ) {
          segmentNumber = i + 1;
          break;
        }
      }

      console.log(
        "The roulette stopped at segment " +
          segmentNumber +
          " (color: " +
          colors[segmentNumber - 1] +
          ")"
      );
    }
  }

  requestAnimationFrame(animation);
}



// Draw the initial roulette
drawRoulette();

spinButton.addEventListener("click", spin);
.roulette-wrapper {
  position: relative;
  background: #ccc;
  height: 720px;
  width: 720px;
}

#spin-button {
  position: absolute;
}

.roulette-wrapper .top-image {
  position: absolute;
  left: 50%;
  top: -30px;
  transform: translateX(-50%);
  z-index: 777;
}

.roulette-wrapper .center-image {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
}

.roulette-wrapper canvas {
  position: absolute;
  background-color: white;
  border-radius: 50%;
}
<div class="roulette-wrapper">
    <button id="spin-button">Spin</button>
    <img class="top-image" src="https://sonhodospes.vteximg.com.br/arquivos/top-arrow.svg" alt="top-image"/>
  <canvas id="roulette-canvas" width="720" height="720"></canvas>
  <img class="center-image" src="https://sonhodospes.vteximg.com.br/arquivos/coupon-spin-center-full.svg" alt="center-image"/>

</div>

2

Answers


  1. One "dirty" solution is to use getImageData.data to get the RGB at position X,Y just below the bottom center of the arrow.

    Then use that to determine the name of the color. I’m sure there is another way to get the name from the RGB, just this is simple and works.

    const canvas = document.getElementById("roulette-canvas");
    const ctx = canvas.getContext("2d");
    const spinButton = document.getElementById("spin-button");
    const segments = 5;
    const segmentSize = 360 / segments;
    const colors = ["red", "yellow", "blue", "orange", "purple"];
    const spinTime = 4500; // Spin time in milliseconds
    const decelerationRate = 0.97; // Rate at which velocity decreases
    
    let colorRGB = {
      "255,0,0" : "red",
      "0,0,255" : "blue",
      "255,255,0" : "yellow",
      "128,0,128" : "purple",
      "255,165,0" : "orange"
    };
    
    let arrow = document.querySelector(".top-image");
    
    const rect = arrow.getBoundingClientRect();
    const arrowCenter = rect.left + window.scrollX - arrow.offsetWidth
      
    
    let degree = 0;
    let rotation = 0;
    let velocity = 0;
    let spinning = false;
    
    function drawRoulette() {
      ctx.beginPath();
      ctx.arc(canvas.width / 2, canvas.height / 2, 353, 0, 2 * Math.PI);
      ctx.strokeStyle = "#D3A466"; // Golden color
      ctx.lineWidth = 12;
      ctx.stroke();
    
      // Draw the segments
      for (let i = 0; i < segments; i++) {
        ctx.fillStyle =  colors[i];
        
        ctx.beginPath();
        ctx.moveTo(canvas.width / 2, canvas.height / 2);
        ctx.arc(
          canvas.width / 2,
          canvas.height / 2,
          350,
          (segmentSize * i - 90) * Math.PI / 180,
          (segmentSize * (i + 1) - 90) * Math.PI / 180
        );
        ctx.lineTo(canvas.width / 2, canvas.height / 2);
        ctx.fill();
      }
    }
    
    function spin() {
      if (spinning) return;
    
      spinning = true;
    
      // Random number between 1 and 360
      degree = Math.floor(Math.random() * 360) + 1;
      velocity = 10; // Initial velocity
    
      let start = null;
      let stopAnimation = false;
    
      function animation(timestamp) {
        if (!start) start = timestamp;
        let progress = timestamp - start;
        rotation += (velocity * Math.PI) / 180 / 20; // Increase rotation based on velocity
        velocity *= decelerationRate; // Decrease velocity based on deceleration rate
    
        // Stop the animation when velocity becomes too low
        if (velocity < 0.1) {
          velocity = 0;
          spinning = false;
        }
    
        if (!stopAnimation && progress >= spinTime - 1500) {
          // Start gradually slowing down the animation one second before it stops
          velocity *= decelerationRate;
          stopAnimation = true;
        }
    
        if (progress >= spinTime - 2000) velocity = velocity - 2;
    
        ctx.clearRect(0, 0, canvas.width, canvas.height);
        ctx.translate(canvas.width / 2, canvas.height / 2);
        ctx.rotate(rotation);
        ctx.translate(-canvas.width / 2, -canvas.height / 2);
        drawRoulette();
    
        if (progress < spinTime) {
          requestAnimationFrame(animation);
        } else {
          // Calculate the segment that the roulette stopped at
          let stopDegree = (degree - rotation / Math.PI * 180) % 360;
          let segmentNumber = 0;
    
          for (let i = 0; i < segments; i++) {
            if (
              stopDegree >= segmentSize * i &&
              stopDegree < segmentSize * (i + 1)
            ) {
              segmentNumber = i + 1;
              break;
            }
          }
          
          var data = ctx.getImageData(arrowCenter, arrow.offsetHeight, 1, 1).data;
          let selColor = data[0] + "," + data[1] + "," + data[2];
    
          console.log(
            "The roulette stopped at segment " +
              colorRGB[selColor]
          );
        }
      }
    
      requestAnimationFrame(animation);
    }
    
    
    
    // Draw the initial roulette
    drawRoulette();
    
    spinButton.addEventListener("click", spin);
    .roulette-wrapper {
      position: relative;
      background: #ccc;
      height: 720px;
      width: 720px;
    }
    
    #spin-button {
      position: absolute;
    }
    
    .roulette-wrapper .top-image {
      position: absolute;
      left: 50%;
      top: -30px;
      transform: translateX(-50%);
      z-index: 777;
    }
    
    .roulette-wrapper .center-image {
      position: absolute;
      left: 50%;
      top: 50%;
      transform: translate(-50%, -50%);
    }
    
    .roulette-wrapper canvas {
      position: absolute;
      background-color: white;
      border-radius: 50%;
    }
    <div class="roulette-wrapper">
        <button id="spin-button">Spin</button>
        <img class="top-image" src="https://sonhodospes.vteximg.com.br/arquivos/top-arrow.svg" alt="top-image"/>
      <canvas id="roulette-canvas" width="720" height="720"></canvas>
      <img class="center-image" src="https://sonhodospes.vteximg.com.br/arquivos/coupon-spin-center-full.svg" alt="center-image"/>
    
    </div>
    Login or Signup to reply.
  2. There are these issues:

    • The rotation you apply to the canvas is cumulative. I’m not referring to rotation +=, but to ctx.rotate: that adds the given rotation argument value to the current transformation of the canvas. So here you lose track of the absolute rotation that is in effect explaining why you get the wrong color notification at the end. To avoid this accumulation, reset the transformation before you call ctx.rotation. When you do this, you’ll notice you’ll need to increase the initial velocity to compensate for this correction.

    • The variable degree is only used in determining the final color, which makes no sense as it is a random number. So remove this variable.

    • The initial velocity is always the same. It would make more sense to make it a random value, like between 20 and 100.

    • When you add to the rotation, you divide by some "magic" number 20. Don’t do this. Instead choose a more appropriate velocity.

    • The if block that tests progress >= spinTime - 1500 will only execute once, since it sets stopAnimation to true. So the effect of this block is negligible. Remove it.

    • The assignment velocity = velocity - 2; will also be executed when the velocity is already tiny, making it negative. This is not right: a negative velocity would signify a backwards movement. You’ll want to always have a non-negative velocity. So remove this.

    • You have several stop conditions, like spinning, spinTime, stopAnimation, … Just use spinning only.

    • The stopDegree calculation could give a negative value, because % in JavaScript is a remainder operator, not a modulo operator. So subtract from 360 to make sure the result remains non-negative.

    Here is your snippet with the above changes. Comments in Capitals indicate where changes were made:

      const canvas = document.getElementById("roulette-canvas");
      const ctx = canvas.getContext("2d");
      const spinButton = document.getElementById("spin-button");
      const segments = 5;
      const segmentSize = 360 / segments;
      const colors = ["red", "yellow", "blue", "orange", "purple"];
      const spinTime = 4500; // Spin time in milliseconds
      const decelerationRate = 0.97; // Rate at which velocity decreases
      let degree = 0;
      let rotation = 0;
      let velocity = 0;
      let spinning = false;
    
      function drawRoulette() {
        ctx.beginPath();
        ctx.arc(canvas.width / 2, canvas.height / 2, 353, 0, 2 * Math.PI);
        ctx.strokeStyle = "#D3A466"; // Golden color
        ctx.lineWidth = 12;
        ctx.stroke();
    
        // Draw the segments
        for (let i = 0; i < segments; i++) {
          ctx.fillStyle = colors[i];
          ctx.beginPath();
          ctx.moveTo(canvas.width / 2, canvas.height / 2);
          ctx.arc(
            canvas.width / 2,
            canvas.height / 2,
            350,
            (segmentSize * i - 90) * Math.PI / 180,
            (segmentSize * (i + 1) - 90) * Math.PI / 180
          );
          ctx.lineTo(canvas.width / 2, canvas.height / 2);
          ctx.fill();
        }
      }
    
      function spin() {
        if (spinning) return;
    
        spinning = true;
    
        // Random number between 1 and 360
        // NOT USED IN MEANINGFUL WAY: degree = Math.floor(Math.random() * 360) + 1;
        // NOT RANDOM: velocity = 10; // Initial velocity
        velocity = Math.floor(Math.random() * 80) + 20; // Initial velocity (20-100)
    
        let start = null;
        let stopAnimation = false;
    
        function animation(timestamp) {
          if (!start) start = timestamp;
          let progress = timestamp - start;
          // NO MAGIC NUMBERS (20): rotation += (velocity * Math.PI) / 180 / 20;
          rotation += (velocity * Math.PI) / 180; // Increase rotation based on velocity
          velocity *= decelerationRate; // Decrease velocity based on deceleration rate
    
          // Stop the animation when velocity becomes too low
          if (velocity < 0.1) {
            velocity = 0;
            spinning = false;
          }
    
    /*
          // NOT ONE EXTRA DECELERATION
          if (!stopAnimation && progress >= spinTime - 1500) {
            // Start gradually slowing down the animation one second before it stops
            velocity *= decelerationRate;
            stopAnimation = true;
          }
    
          // NO NEGATIVE VELOCITY
          if (progress >= spinTime - 2000) velocity = velocity - 2;
    */
          // RESET ROTATION:
          ctx.resetTransform(); 
          ctx.clearRect(0, 0, canvas.width, canvas.height);
          ctx.translate(canvas.width / 2, canvas.height / 2);
          ctx.rotate(rotation);
          ctx.translate(-canvas.width / 2, -canvas.height / 2);
          drawRoulette();
    
    
          // ONLY USE spinning FOR ENDING: if (progress < spinTime) {
          if (spinning) {
            requestAnimationFrame(animation);
          } else {
            // Calculate the segment that the roulette stopped at
            // DON'T USE degree: let stopDegree = (degree - rotation / Math.PI * 180) % 360;
            let stopDegree = 360 - (rotation / Math.PI * 180) % 360;
            let segmentNumber = 0;
    
            for (let i = 0; i < segments; i++) {
              if (
                stopDegree >= segmentSize * i &&
                stopDegree < segmentSize * (i + 1)
              ) {
                segmentNumber = i + 1;
                break;
              }
            }
    
            console.log(
              "The roulette stopped at segment " +
                segmentNumber +
                " (color: " +
                colors[segmentNumber - 1] +
                ")"
            );
          }
        }
    
        requestAnimationFrame(animation);
      }
    
      // Draw the initial roulette
      drawRoulette();
    
      spinButton.addEventListener("click", spin);
    .roulette-wrapper {
      position: relative;
      background: #ccc;
      height: 720px;
      width: 720px;
    }
    
    #spin-button {
      position: absolute;
    }
    
    .roulette-wrapper .top-image {
      position: absolute;
      left: 50%;
      top: -30px;
      transform: translateX(-50%);
      z-index: 777;
    }
    
    .roulette-wrapper .center-image {
      position: absolute;
      left: 50%;
      top: 50%;
      transform: translate(-50%, -50%);
    }
    
    .roulette-wrapper canvas {
      position: absolute;
      background-color: white;
      border-radius: 50%;
    }
    <div class="roulette-wrapper">
        <button id="spin-button">Spin</button>
        <img class="top-image" src="https://sonhodospes.vteximg.com.br/arquivos/top-arrow.svg" alt="top-image"/>
      <canvas id="roulette-canvas" width="720" height="720"></canvas>
      <img class="center-image" src="https://sonhodospes.vteximg.com.br/arquivos/coupon-spin-center-full.svg" alt="center-image"/>
    
    </div>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search