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:
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
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.
There are these issues:
The rotation you apply to the canvas is cumulative. I’m not referring to
rotation +=
, but toctx.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 callctx.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 testsprogress >= spinTime - 1500
will only execute once, since it setsstopAnimation
totrue
. 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 usespinning
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: