Remove the value from the wheel of fortune - javascript

I have used the wheel of fortune code by Roko C. Buljan from here: how to draw a wheel of fortune?
I'm new to using canvas but I've figured out most of the what the code is doing - maths is defo not my forte!
I'm struggling to add the functionality that when the wheel has stopped spinning and has landed on the slice, how can I either remove it completely or change the colour of the slice and stop the wheel landing on it again?
Is this possible?
Thanks for your answers/advice in advance!
const fruits = [{
color: '#cf6f',
label: 'Apple',
value: 1
},
{
color: '#0051',
label: 'Lemon',
value: 2
},
{
color: '#efd',
label: 'Raspberry',
value: 3
},
{
color: '#6b9',
label: 'Blueberry',
value: 4
},
{
color: '#afb',
label: 'Mango',
value: 5
},
];
const rand = (min, max) => Math.random() * (max - min) + min;
const numOfFruits = fruits.length;
const spin = document.querySelector('#spin');
const ctx = document.querySelector('#wheel').getContext('2d');
const diameter = ctx.canvas.width;
const radius = diameter / 2;
const PI = Math.PI; // 3.141592653589793
const TAU = 2 * PI; // 6.283185307179586
const arc = TAU / fruits.length; // 0.8975979010256552
const friction = 0.97; // 0.995=soft, 0.99=mid, 0.98=hard
let angVel = 0; // Angular velocity
let angle = 0; // angle in radians
const getIndex = () =>
Math.floor(numOfFruits - (angle / TAU) * numOfFruits) % numOfFruits;
function drawSector(sector, index) {
const angle = arc * index;
console.log('angle', angle)
console.log(index)
console.log(sector)
ctx.save();
// COLOR
ctx.beginPath();
ctx.fillStyle = sector.color;
ctx.moveTo(radius, radius);
ctx.arc(radius, radius, radius, angle, angle + arc);
ctx.lineTo(radius, radius);
ctx.fill();
// positioning of the text
ctx.translate(radius, radius);
ctx.rotate(angle + arc / 2);
ctx.textAlign = 'right';
ctx.fillStyle = '#243447';
ctx.font = 'bold 1.3em Courier New';
ctx.fillText(sector.label, radius - 10, 10);
//
ctx.restore();
}
function rotate() {
const slice = fruits[getIndex()];
ctx.canvas.style.transform = `rotate(${angle - PI / 2}rad)`;
spin.textContent = !angVel ? 'SPIN' : slice.label;
spin.style.background = slice.color;
}
// Called when the wheel stops
function stopSpinning() {
const slice = fruits[getIndex()];
console.log('Landed on', slice.label);
}
function frame() {
if (!angVel) return;
const isSpinning = angVel > 0;
angVel *= friction; // Decrement velocity by friction
if (angVel < 0.002) angVel = 0; // Bring to stop
angle += angVel; // Update angle
angle %= TAU; // Normalize angle
rotate();
if (isSpinning && angVel === 0) {
// If the wheel has stopped spinning
stopSpinning();
}
}
const engine = () => {
frame();
requestAnimationFrame(engine);
};
// INIT
fruits.forEach(drawSector);
rotate(); // Initial rotation
engine(); // Start engine
spin.addEventListener('click', () => {
if (!angVel) {
angVel = rand(2, 1);
}
});
<div id="wheelOfFortune">
<canvas id="wheel" width="400" height="400"></canvas>
<div id="spin">SPIN</div>
</div>

In your stopSpinning we could just remove the item that it landed on, we do that with:
.splice(getIndex(),1)
if you never use it before, read more here:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice
I also had to do a few more changes to accomodate the fact that now the array changes, for example the const numOfFruits = fruits.length instead of using that we just use the length directly when we need it
Try this code below:
let fruits = [
{color: 'red', value: 1 },
{color: 'blue', value: 2 },
{color: 'pink', value: 3 },
{color: 'green', value: 4 },
{color: 'cyan', value: 5 },
];
const rand = (min, max) => Math.random() * (max - min) + min;
const spin = document.querySelector('#spin');
const canvas = document.querySelector('#wheel')
const ctx = canvas.getContext('2d');
const radius = canvas.width / 2;
const PI = Math.PI; // 3.141592653589793
const TAU = 2 * PI; // 6.283185307179586
const friction = 0.97; // 0.995=soft, 0.99=mid, 0.98=hard
let angVel = 0; // Angular velocity
let angle = 0; // angle in radians
const getIndex = () =>
Math.floor(fruits.length - (angle / TAU) * fruits.length) % fruits.length;
// Called when the wheel stops
function stopSpinning() {
const slice = fruits[getIndex()];
console.log('Landed on', slice.value);
fruits.splice(getIndex(),1)
init()
}
function drawSector(sector, index) {
const angle = (TAU / fruits.length) * index;
ctx.save();
// COLOR
ctx.beginPath();
ctx.fillStyle = sector.color;
ctx.moveTo(radius, radius);
ctx.arc(radius, radius, radius, angle, angle + TAU / fruits.length);
ctx.lineTo(radius, radius);
ctx.fill();
// positioning of the text
ctx.translate(radius, radius);
ctx.rotate(angle + (TAU / fruits.length) / 2);
ctx.textAlign = 'right';
ctx.fillStyle = 'black';
ctx.font = 'bold 1.3em Courier New';
ctx.fillText(sector.value, radius - 10, 10);
//
ctx.restore();
}
function rotate() {
const slice = fruits[getIndex()];
ctx.canvas.style.transform = `rotate(${angle - PI / 2}rad)`;
spin.textContent = !angVel ? 'SPIN' : slice.value;
}
function frame() {
if (!angVel) return;
const isSpinning = angVel > 0;
angVel *= friction; // Decrement velocity by friction
if (angVel < 0.002) angVel = 0; // Bring to stop
angle += angVel; // Update angle
angle %= TAU; // Normalize angle
rotate();
if (isSpinning && angVel === 0) {
// If the wheel has stopped spinning
stopSpinning();
}
}
function init() {
fruits.forEach(drawSector);
rotate(); // Initial rotation
engine(); // Start engine
}
const engine = () => {
frame();
requestAnimationFrame(engine);
};
init()
spin.addEventListener('click', () => {
if (!angVel) angVel = rand(2, 1);
});
<div id="wheelOfFortune">
<canvas id="wheel" width="100" height="100"></canvas>
<button id="spin">SPIN</button>
</div>

Related

How to smoothly scroll repeating linear gradient on canvas 2D?

I’m using context.createLinearGradient to create gradients, and to make it scroll I'm animating the colorStops. But the issue is when a color reaches the end, if I wrap it around back to start the whole gradient changes.
In CSS I could avoid this using repeating-linear-gradient and it would work but I havent figured out a way to do this without the sudden color changes at the edges. I tried drawing it a little bit offscreen but It still off.
This is what I have so far:
const colors = [
{ color: "#FF0000", pos: 0 },
{ color: "#FFFF00", pos: 1 / 5 },
{ color: "#00FF00", pos: 2 / 5 },
{ color: "#0000FF", pos: 3 / 5 },
{ color: "#FF00FF", pos: 4 / 5 },
{ color: "#FF0000", pos: 1 },
];
const angleStep = 0.2;
const linearStep = 0.001;
function init() {
const canvas = document.querySelector("canvas");
const context = canvas.getContext("2d");
const mw = canvas.width;
const mh = canvas.height;
let angle = 0;
function drawScreen() {
angle = (angle + angleStep) % 360;
const [x1, y1, x2, y2] = angleToPoints(angle, mw, mh);
const gradient = context.createLinearGradient(x1, y1, x2, y2);
for (const colorStop of colors) {
gradient.addColorStop(colorStop.pos, colorStop.color);
colorStop.pos += linearStep;
if (colorStop.pos > 1) colorStop.pos = 0;
}
context.fillStyle = gradient;
context.fillRect(0, 0, canvas.width, canvas.height);
}
function loop() {
drawScreen()
window.requestAnimationFrame(loop);
}
loop();
}
function angleToPoints(angle, width, height){
const rad = ((180 - angle) / 180) * Math.PI;
// This computes the length such that the start/stop points will be at the corners
const length = Math.abs(width * Math.sin(rad)) + Math.abs(height * Math.cos(rad));
// Compute the actual x,y points based on the angle, length of the gradient line and the center of the div
const halfx = (Math.sin(rad) * length) / 2.0
const halfy = (Math.cos(rad) * length) / 2.0
const cx = width / 2.0
const cy = height / 2.0
const x1 = cx - halfx
const y1 = cy - halfy
const x2 = cx + halfx
const y2 = cy + halfy
return [x1, y1, x2, y2];
}
init();
html,body, canvas {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
<canvas width="128" height="72"></canvas>
The problem is that the gradients you create don't usually have stops at 0 or 1. When a gradient doesn't have those stops, the ends get filled out by whatever the color is of the closest stop.
To fill them in the way you want, you'd need to figure out what the color at the crossover point should be and add it to both ends.
Below, we determine the current end colors by sorting and then use linear interpolation (lerp) to get the crossover color. I've prefixed my meaningful changes with comments that start with // ###.
// ### lerp for hexadecimal color strings
function lerpColor(a, b, amount) {
const
ah = +a.replace('#', '0x'),
ar = ah >> 16,
ag = ah >> 8 & 0xff,
ab = ah & 0xff,
bh = +b.replace('#', '0x'),
br = bh >> 16,
bg = bh >> 8 & 0xff,
bb = bh & 0xff,
rr = ar + amount * (br - ar),
rg = ag + amount * (bg - ag),
rb = ab + amount * (bb - ab)
;
return '#' + (0x1000000 + (rr << 16) + (rg << 8) + rb | 0).toString(16).slice(1);
}
const colors = [
{ color: "#FF0000", pos: 0 },
{ color: "#FFFF00", pos: 1 / 5 },
{ color: "#00FF00", pos: 2 / 5 },
{ color: "#0000FF", pos: 3 / 5 },
{ color: "#FF00FF", pos: 4 / 5 },
{ color: "#FF0000", pos: 1 },
];
const angleStep = 0.2;
const linearStep = 0.005;
function init() {
const canvas = document.querySelector("canvas");
const context = canvas.getContext("2d");
const mw = canvas.width;
const mh = canvas.height;
let angle = 0;
function drawScreen() {
angle = (angle + angleStep) % 360;
const [x1, y1, x2, y2] = angleToPoints(angle, mw, mh);
const gradient = context.createLinearGradient(x1, y1, x2, y2);
for (const colorStop of colors) {
gradient.addColorStop(colorStop.pos, colorStop.color);
colorStop.pos += linearStep;
// ### corrected error here
if (colorStop.pos > 1) colorStop.pos -= 1;
}
// ### compute and set the gradient end stops
const sortedStops = colors.sort((a,b) => a.pos - b.pos);
const firstStop = sortedStops[0];
const lastStop = sortedStops.slice(-1)[0];
const endColor = lerpColor(firstStop.color, lastStop.color, firstStop.pos*5);
gradient.addColorStop(0, endColor);
gradient.addColorStop(1, endColor);
context.fillStyle = gradient;
context.fillRect(0, 0, canvas.width, canvas.height);
}
function loop() {
drawScreen()
requestAnimationFrame(loop)
}
loop();
}
function angleToPoints(angle, width, height){
const rad = ((180 - angle) / 180) * Math.PI;
// This computes the length such that the start/stop points will be at the corners
const length = Math.abs(width * Math.sin(rad)) + Math.abs(height * Math.cos(rad));
// Compute the actual x,y points based on the angle, length of the gradient line and the center of the div
const halfx = (Math.sin(rad) * length) / 2.0
const halfy = (Math.cos(rad) * length) / 2.0
const cx = width / 2.0
const cy = height / 2.0
const x1 = cx - halfx
const y1 = cy - halfy
const x2 = cx + halfx
const y2 = cy + halfy
return [x1, y1, x2, y2];
}
init();
html, body, canvas { width: 100%; height: 100%; margin: 0; padding: 0; }
<canvas width="128" height="72"></canvas>

Why bluring all circle isn`t only stroke of circle

context.filter = "blur(2px)";
Why this ^ element blur all circle space not only edges
I want to make circles blur on edges to look like blured street lights
i fetch stroke in
if (stroke) { context.lineWidth = 1; context.strokeStyle = stroke; context.stroke();}
and set the value in function invoke
createCircle(x + Math.random() * 30 + 10, y - Math.random() * 50, (context.filter = "blur(2px)"));
EDIT: I want to blur only the edges of cirlce not all space(fill) to look like street light
EDIT::: ATTACH FULL CODE
let Drawing = false;
const fireworks = document.querySelector(".fireworks");
const context = fireworks.getContext("2d");
let x;
let y;
let numberCircles = 5;
const colors = ["#780000", "#c1121f", "#fdf0d5", "#003049", "#669bbc"];
//function seting window Size
function windowSize() {
fireworks.width = window.innerWidth * 2;
fireworks.height = window.innerHeight * 2;
fireworks.style.height = window.innerHeight + "px";
fireworks.style.width = window.innerWidth + "px";
context.scale(2, 2);
// console.log("windowSize");
}
//create circle
function createCircle(x, y, stroke) {
context.beginPath();
radius = Math.random() * 5 + 3;
let fill = colors[Math.round(Math.random() * colors.length - 1)];
context.arc(x, y, radius, 0, 2 * Math.PI, false);
if (fill) {
context.fillStyle = fill;
context.fill();
}
if (stroke) {
context.lineWidth = 1;
context.strokeStyle = stroke;
context.stroke();
}
}
//make circle spray DEPEND on angle
function sprayCircle() {
for (i = 0; i < numberCircles; i++) {
let los = Math.round(Math.random() * 3);
switch (los) {
case 0:
createCircle(x + Math.random() * 30 + 10, y + Math.random() * 50, (context.filter = "blur(2px)"));
break;
case 1:
createCircle(x + Math.random() * 30 + 10, y - Math.random() * 50, (context.filter = "blur(2px)"));
break;
case 2:
createCircle(x - Math.random() * 5, y - Math.random() * 5);
break;
}
}
}
//mouse follow
fireworks.addEventListener("mousedown", function (e) {
Drawing = true;
});
fireworks.addEventListener("mousemove", function (e) {
if (Drawing === true) {
sprayCircle();
x = e.offsetX;
y = e.offsetY;
}
});
window.addEventListener("mouseup", function (e) {
if (Drawing === true) {
Drawing = false;
}
});
//end
windowSize();
window.addEventListener("resize", windowSize, false);
<canvas class="fireworks" width="560" height="868"></canvas>

Animating multiple circles in a canvas

I'm trying to make an animation inside a canvas: here, a numbered circle must be drawn and move from left to right one single time, disappearing as soon as it reaches the end of animation.
For now I managed to animate it in loop, but I need to animate at the same time (or with a set delay) multiple numbered circles, strating in different rows (changing y position) so they wont overlap.
Any idea how can I manage this? my JS code is the following:
// Single Animated Circle - Get Canvas element by Id
var canvas = document.getElementById("canvas");
// Set Canvas dimensions
canvas.width = 300;
canvas.height = 900;
// Get drawing context
var ctx = canvas.getContext("2d");
// Radius
var radius = 13;
// Starting Position
var x = radius;
var y = radius;
// Speed in x and y direction
var dx = 1;
var dy = 0;
// Generate random number
var randomNumber = Math.floor(Math.random() * 60) + 1;
if (randomNumber > 0 && randomNumber <= 10) {
ctx.strokeStyle = "#0b0bf1";
} else if (randomNumber > 10 && randomNumber <= 20) {
ctx.strokeStyle = "#f10b0b";
} else if (randomNumber > 20 && randomNumber <= 30) {
ctx.strokeStyle = "#0bf163";
} else if (randomNumber > 30 && randomNumber <= 40) {
ctx.strokeStyle = "#f1da0b";
} else if (randomNumber > 40 && randomNumber <= 50) {
ctx.strokeStyle = "#950bf1";
} else if (randomNumber > 50 && randomNumber <= 60) {
ctx.strokeStyle = "#0bf1e5";
}
function animate3() {
requestAnimationFrame(animate3);
ctx.clearRect(0, 0, 300, 900);
if (x + radius > 300 || x - radius < 0) {
x = radius;
}
x += dx;
ctx.beginPath();
ctx.arc(x, y, 12, 0, Math.PI * 2, false);
ctx.stroke();
ctx.fillText(randomNumber, x - 5, y + 3);
}
// Animate the Circle
animate3();
<canvas id="canvas"></canvas>
Here is a solution which doesn't use classes as such and separates the animation logic from the updating - which can be useful if you want more precise control over timing.
// Some helper functions
const clamp = (number, min, max) => Math.min(Math.max(number, min), max);
// Choose and remove random member of arr with equal probability
const takeRandom = arr => arr.splice(parseInt(Math.random() * arr.length), 1)[0]
// Call a function at an interval, passing the amount of time that has passed since the last call
function update(callBack, interval) {
let now = performance.now();
let last;
setInterval(function() {
last = now;
now = performance.now();
callBack((now - last) / 1000);
})
}
const settings = {
width: 300,
height: 150,
radius: 13,
gap: 5,
circles: 5,
maxSpeed: 100,
colors: ["#0b0bf1", "#f10b0b", "#0bf163", "#f1da0b", "#950bf1", "#0bf1e5"]
};
const canvas = document.getElementById("canvas");
canvas.width = settings.width;
canvas.height = settings.height;
const ctx = canvas.getContext("2d");
// Set circle properties
const circles = [...Array(settings.circles).keys()].map(i => ({
number: i + 1,
x: settings.radius,
y: settings.radius + (settings.radius * 2 + settings.gap) * i,
radius: settings.radius,
dx: settings.maxSpeed * Math.random(), // This is the speed in pixels per second
dy: 0,
color: takeRandom(settings.colors)
}));
function drawCircle(circle) {
ctx.strokeStyle = circle.color;
ctx.beginPath();
ctx.arc(circle.x, circle.y, circle.radius, 0, Math.PI * 2, false);
ctx.stroke();
ctx.fillText(circle.number, circle.x - 5, circle.y + 3);
}
function updateCircle(circle, dt) {
// Update a circle's position after dt seconds
circle.x = clamp(circle.x + circle.dx * dt, circle.radius + 1, settings.width - circle.radius - 1);
circle.y = clamp(circle.y + circle.dy * dt, circle.radius + 1, settings.height - circle.radius - 1);
}
function animate() {
ctx.clearRect(0, 0, settings.width, settings.height);
circles.forEach(drawCircle);
requestAnimationFrame(animate);
}
update(dt => circles.forEach(circle => updateCircle(circle, dt)), 50);
animate();
<canvas id="canvas" style="border: solid 1px black"></canvas>
Here I transformed your sample code into a class ...
We pass all the data as a parameter, you can see that in the constructor, I simplified a lot of your code to keep it really short, but all the same drawing you did is there in the draw function
Then all we need to do is create instances of this class and call the draw function inside that animate3 loop you already have.
You had a hardcoded value on the radius:
ctx.arc(x, y, 12, 0, Math.PI * 2, false)
I assume that was a mistake and fix it on my code
var canvas = document.getElementById("canvas");
canvas.width = canvas.height = 300;
var ctx = canvas.getContext("2d");
class Circle {
constructor(data) {
this.data = data
}
draw() {
if (this.data.x + this.data.radius > 300 || this.data.x - this.data.radius < 0) {
this.data.x = this.data.radius;
}
this.data.x += this.data.dx;
ctx.beginPath();
ctx.arc(this.data.x, this.data.y, this.data.radius, 0, Math.PI * 2, false);
ctx.stroke();
ctx.fillText(this.data.number, this.data.x - 5, this.data.y + 3);
}
}
circles = []
circles.push(new Circle({radius:13, x: 10, y: 15, dx: 1, dy: 0, number: "1"}))
circles.push(new Circle({radius:10, x: 10, y: 50, dx: 2, dy: 0, number: "2"}))
function animate3() {
requestAnimationFrame(animate3);
ctx.clearRect(0, 0, canvas.width, canvas.height);
circles.forEach(item => item.draw());
}
animate3();
<canvas id="canvas"></canvas>
Code should be easy to follow let me know if you have any questions

Move an object through set of coordinates on HTML5 Canvas

I want to move a object (circle in this case) through array of coordinates (for example: {(300,400), (200,300), (300,200),(400,400)})on HTML5 Canvas. I could move the object to one coordinate as follows. The following code draws a circle at (100,100) and moves it to (300,400). I am stuck when trying to extend this so that circle moves through set of coordinates one after the other.
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
//circle object
let circle ={
x:100,
y:100,
radius:10,
dx:1,
dy:1,
color:'blue'
}
//function to draw above circle on canvas
function drawCircle(){
ctx.beginPath();
ctx.arc(circle.x,circle.y,circle.radius,0,Math.PI*2);
ctx.fillStyle=circle.color;
ctx.fill();
ctx.closePath();
}
//Moving to a target coordinate (targetX,targetY)
function goTo(targetX,targetY){
if(Math.abs(circel.x-targetX)<circle.dx && Math.abs(circel.y-targetY)<circle.dy){
circle.dx=0;
circle.dy=0;
circel.x = targetX;
circle.y = targetY;
}
else{
const opp = targetY - circle.y;
const adj = targetX - circle.x;
const angle = Math.atan2(opp,adj)
circel.x += Math.cos(angle)*circle.dx
circle.y += Math.sin(angle)*circle.dy
}
}
function update(){
ctx.clearRect(0,0,canvas.width,canvas.height);
drawCircle()
goTo(300,400)
requestAnimationFrame(update);
}
update()
Random access key frames
For the best control of animations you need to create way points (key frames) that can be accessed randomly by time. This means you can get any position in the animation just by setting the time.
You can then play and pause, set speed, reverse and seek to any position in the animation.
Example
The example below uses a set of points and adds data required to quickly locate the key frames at the requested time and interpolate the position.
The blue dot will move at a constant speed over the path in a time set by pathTime in this case 4 seconds.
The red dot's position is set by the slider. This is to illustrate the random access of the animation position.
const ctx = canvas.getContext('2d');
const pathTime = 4; // Total time to travel path from start to end in seconds
var startTime, animTime = 0, paused = false;
requestAnimationFrame(update);
const P2 = (x, y) => ({x, y, dx: 0,dy: 0,dist: 0, start: 0, end: 0});
const pathCoords = [
P2(20, 20), P2(100, 50),P2(180, 20), P2(150, 100), P2(180, 180),
P2(100, 150), P2(20, 180), P2(50, 100), P2(20, 20),
];
createAnimationPath(pathCoords);
const circle ={
draw(rad = 10, color = "blue") {
ctx.fillStyle = color;
ctx.beginPath();
ctx.arc(this.x, this.y, rad, 0, Math.PI * 2);
ctx.fill();
}
};
function createAnimationPath(points) { // Set up path for easy random position lookup
const segment = (prev, next) => {
[prev.dx, prev.dy] = [next.x - prev.x, next.y - prev.y];
prev.dist = Math.hypot(prev.dx, prev.dy);
next.end = next.start = prev.end = prev.start + prev.dist;
}
var i = 1;
while (i < points.length) { segment(points[i - 1], points[i++]) }
}
function getPos(path, pos, res = {}) {
pos = (pos % 1) * path[path.length - 1].end; // loop & scale to total length
const pathSeg = path.find(p => pos >= p.start && pos <= p.end);
const unit = (pos - pathSeg.start) / pathSeg.dist; // unit distance on segment
res.x = pathSeg.x + pathSeg.dx * unit; // x, y position on segment
res.y = pathSeg.y + pathSeg.dy * unit;
return res;
}
function update(time){
// startTime ??= time; // Throws syntax on iOS
startTime = startTime ?? time; // Fix for above
ctx.clearRect(0, 0, canvas.width, canvas.height);
if (paused) { startTime = time - animTime }
else { animTime = time - startTime }
getPos(pathCoords, (animTime / 1000) / pathTime, circle).draw();
getPos(pathCoords, timeSlide.value, circle).draw(5, "red");
requestAnimationFrame(update);
}
pause.addEventListener("click", ()=> { paused = true; pause.classList.add("pause") });
play.addEventListener("click", ()=> { paused = false; pause.classList.remove("pause") });
rewind.addEventListener("click", ()=> { startTime = undefined; animTime = 0 });
div {
position:absolute;
top: 5px;
left: 20px;
}
#timeSlide {width: 360px}
.pause {color:blue}
button {height: 30px}
<div><input id="timeSlide" type="range" min="0" max="1" step="0.001" value="0" width= "200"><button id="rewind">Start</button><button id="pause">Pause</button><button id="play">Play</button></div>
<canvas id="canvas" width="200" height="200"></canvas>
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
// array of path coords
const pathCoords = [
[200,100],
[300, 150],
[200,190],
[400,100],
[50,10],
[150,10],
[0, 50],
[500,90],
[20,190],
[10,180],
];
// current point
let currentTarget = pathCoords.shift();
//circle object
const circle ={
x:10,
y:10,
radius:10,
dx:2,
dy:2,
color:'blue'
}
//function to draw above circle on canvas
function drawCircle(){
ctx.beginPath();
ctx.arc(circle.x,circle.y,circle.radius,0,Math.PI*2);
ctx.fillStyle=circle.color;
ctx.fill();
ctx.closePath();
}
//Moving to a target coordinate (targetX,targetY)
function goTo(targetX, targetY){
if(Math.abs(circle.x-targetX)<circle.dx && Math.abs(circle.y-targetY)<circle.dy){
// dont stop...
//circle.dx = 0;
//circle.dy = 0;
circle.x = targetX;
circle.y = targetY;
// go to next point
if (pathCoords.length) {
currentTarget = pathCoords.shift();
} else {
console.log('Path end');
}
} else {
const opp = targetY - circle.y;
const adj = targetX - circle.x;
const angle = Math.atan2(opp,adj)
circle.x += Math.cos(angle)*circle.dx
circle.y += Math.sin(angle)*circle.dy
}
}
function update(){
ctx.clearRect(0,0,canvas.width,canvas.height);
drawCircle();
goTo(...currentTarget);
requestAnimationFrame(update);
}
update();
<canvas id=canvas width = 500 height = 200></canvas>

How can we stop this HTML5 Canvas wheel at exact points after spin?

In the Below code link HTML5 canvas spin wheel game. I want to stop this canvas at a user-defined position as if the user wants to stop always at 200 texts or 100 texts like that.
Currently, it is stopping at random points I want to control where to stop as in if I want to stop circle at 100 or 200 or 0 whenever I want.
How can we achieve that??? Can anyone Help!!!!!
Attached Codepen link also.
Html file
<div>
<canvas class="spin-wheel" id="canvas" width="300" height="300"></canvas>
</div>
JS file
var color = ['#ca7','#7ac','#77c','#aac','#a7c','#ac7', "#caa"];
var label = ['10', '200','50','100','5','500',"0"];
var slices = color.length;
var sliceDeg = 360/slices;
var deg = 270;
var speed = 5;
var slowDownRand = 0;
var ctx = canvas.getContext('2d');
var width = canvas.width; // size
var center = width/2; // center
var isStopped = false;
var lock = false;
function rand(min, max) {
return Math.random() * (max - min) + min;
}
function deg2rad(deg){ return deg * Math.PI/180; }
function drawSlice(deg, color){
ctx.beginPath();
ctx.fillStyle = color;
ctx.moveTo(center, center);
ctx.arc(center, center, width/2, deg2rad(deg), deg2rad(deg+sliceDeg));
console.log(center, center, width/2, deg2rad(deg), deg2rad(deg+sliceDeg))
ctx.lineTo(center, center);
ctx.fill();
}
function drawText(deg, text) {
ctx.save();
ctx.translate(center, center);
ctx.rotate(deg2rad(deg));
ctx.textAlign = "right";
ctx.fillStyle = "#fff";
ctx.font = 'bold 30px sans-serif';
ctx.fillText(text, 130, 10);
ctx.restore();
}
function drawImg() {
ctx.clearRect(0, 0, width, width);
for(var i=0; i<slices; i++){
drawSlice(deg, color[i]);
drawText(deg+sliceDeg/2, label[i]);
deg += sliceDeg;
}
}
// ctx.rotate(360);
function anim() {
isStopped = true;
deg += speed;
deg %= 360;
// Increment speed
if(!isStopped && speed<3){
speed = speed+1 * 0.1;
}
// Decrement Speed
if(isStopped){
if(!lock){
lock = true;
slowDownRand = rand(0.994, 0.998);
}
speed = speed>0.2 ? speed*=slowDownRand : 0;
}
// Stopped!
if(lock && !speed){
var ai = Math.floor(((360 - deg - 90) % 360) / sliceDeg); // deg 2 Array Index
console.log(slices)
ai = (slices+ai)%slices; // Fix negative index
return alert("You got:\n"+ label[ai] ); // Get Array Item from end Degree
// ctx.arc(150,150,150,8.302780584487312,9.200378485512967);
// ctx.fill();
}
drawImg();
window.requestAnimationFrame(anim);
}
function start() {
anim()
}
drawImg();
Spin wheel codepen
Ease curves
If you where to plot the wheel position over time as it slows to a stop you would see a curve, a curve that looks like half a parabola.
You can get the very same curve if you plot the value of x squared in the range 0 to 1 as in the next snippet, the red line shows the plot of f(x) => x * x where 0 <= x <= 1
Unfortunately the plot is the wrong way round and needs to be mirrored in x and y. That is simple by changing the function to f(x) => 1 - (1 - x) ** 2 (Click the canvas to get the yellow line)
const size = 200;
const ctx = Object.assign(document.createElement("canvas"),{width: size, height: size / 2}).getContext("2d");
document.body.appendChild(ctx.canvas);
ctx.canvas.style.border = "2px solid black";
plot(getData());
plot(unitCurve(x => x * x), "#F00");
ctx.canvas.addEventListener("click",()=>plot(unitCurve(x => 1 - (1 - x) ** 2), "#FF0"), {once: true});
function getData(chart = []) {
var pos = 0, speed = 9, deceleration = 0.1;
while(speed > 0) {
chart.push(pos);
pos += speed;
speed -= deceleration;
}
return chart;
}
function unitCurve(f,chart = []) {
const step = 1 / 100;
var x = 0;
while(x <= 1) {
chart.push(f(x));
x += step
}
return chart;
}
function plot(chart, col = "#000") {
const xScale = size / chart.length, yScale = size / 2 / Math.max(...chart);
ctx.setTransform(xScale, 0, 0, yScale, 0, 0);
ctx.strokeStyle = col;
ctx.beginPath();
chart.forEach((y,x) => ctx.lineTo(x,y));
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.stroke();
}
In animation this curve is an ease in.
We can create function that uses the ease function, takes the time and returns the position of the wheel. We can provide some additional values that controls how long the wheel will take to stop, the starting position and the all important stop position.
function wheelPos(currentTime, startTime, endTime, startPos, endPos) {
// first scale the current time to a value from 0 to 1
const x = (currentTime - startTime) / (endTime - startTime);
// rather than the square, we will use the square root (this flips the curve)
const xx = x ** (1 / 2);
// convert the value to a wheel position
return xx * (endPos - startPos) + startPos;
}
Demo
The demo puts it in action. Rather than using the square root the function in the demo defines the root as the constant slowDownRate = 2.6. The smaller this value the greater start speed and the slower the end speed. A value of 1 means it will move at a constant speed and then stop. The value must be > 0 and < 1
requestAnimationFrame(mainLoop);
Math.TAU = Math.PI * 2;
const size = 160;
const ctx = Object.assign(document.createElement("canvas"),{width: size, height: size}).getContext("2d");
document.body.appendChild(ctx.canvas);
const stopAt = document.createElement("div")
document.body.appendChild(stopAt);
ctx.canvas.style.border = "2px solid black";
var gTime; // global time
const colors = ["#F00","#F80","#FF0","#0C0","#08F","#00F","#F0F"];
const wheelSteps = 12;
const minSpins = 3 * Math.TAU; // min number of spins before stopping
const spinTime = 6000; // in ms
const slowDownRate = 1 / 1.8; // smaller this value the greater the ease in.
// Must be > 0
var startSpin = false;
var readyTime = 0;
ctx.canvas.addEventListener("click",() => { startSpin = !wheel.spinning });
stopAt.textContent = "Click wheel to spin";
const wheel = { // hold wheel related variables
img: createWheel(wheelSteps),
endTime: performance.now() - 2000,
startPos: 0,
endPos: 0,
speed: 0,
pos: 0,
spinning: false,
set currentPos(val) {
this.speed = (val - this.pos) / 2; // for the wobble at stop
this.pos = val;
},
set endAt(pos) {
this.endPos = (Math.TAU - (pos / wheelSteps) * Math.TAU) + minSpins;
this.endTime = gTime + spinTime;
this.startTime = gTime;
stopAt.textContent = "Spin to: "+(pos + 1);
}
};
function wheelPos(currentTime, startTime, endTime, startPos, endPos) {
const x = ((currentTime - startTime) / (endTime - startTime)) ** slowDownRate;
return x * (endPos - startPos) + startPos;
}
function mainLoop(time) {
gTime = time;
ctx.setTransform(1,0,0,1,0,0);
ctx.clearRect(0, 0, size, size);
if (startSpin && !wheel.spinning) {
startSpin = false;
wheel.spinning = true;
wheel.startPos = (wheel.pos % Math.TAU + Math.TAU) % Math.TAU;
wheel.endAt = Math.random() * wheelSteps | 0;
} else if (gTime <= wheel.endTime) { // wheel is spinning get pos
wheel.currentPos = wheelPos(gTime, wheel.startTime, wheel.endTime, wheel.startPos, wheel.endPos);
readyTime = gTime + 1500;
} else { // wobble at stop
wheel.speed += (wheel.endPos - wheel.pos) * 0.0125;
wheel.speed *= 0.95;
wheel.pos += wheel.speed;
if (wheel.spinning && gTime > readyTime) {
wheel.spinning = false;
stopAt.textContent = "Click wheel to spin";
}
}
// draw wheel
ctx.setTransform(1,0,0,1,size / 2, size / 2);
ctx.rotate(wheel.pos);
ctx.drawImage(wheel.img, -size / 2 , - size / 2);
// draw marker shadow
ctx.setTransform(1,0,0,1,1,4);
ctx.fillStyle = "#0004";
ctx.beginPath();
ctx.lineTo(size - 13, size / 2);
ctx.lineTo(size, size / 2 - 7);
ctx.lineTo(size, size / 2 + 7);
ctx.fill();
// draw marker
ctx.setTransform(1,0,0,1,0,0);
ctx.fillStyle = "#F00";
ctx.beginPath();
ctx.lineTo(size - 13, size / 2);
ctx.lineTo(size, size / 2 - 7);
ctx.lineTo(size, size / 2 + 7);
ctx.fill();
requestAnimationFrame(mainLoop);
}
function createWheel(steps) {
const ctx = Object.assign(document.createElement("canvas"),{width: size, height: size}).getContext("2d");
const s = size, s2 = s / 2, r = s2 - 4;
var colIdx = 0;
for (let a = 0; a < Math.TAU; a += Math.TAU / steps) {
const aa = a - Math.PI / steps;
ctx.fillStyle = colors[colIdx++ % colors.length];
ctx.beginPath();
ctx.moveTo(s2, s2);
ctx.arc(s2, s2, r, aa, aa + Math.TAU / steps);
ctx.fill();
}
ctx.fillStyle = "#FFF";
ctx.beginPath();
ctx.arc(s2, s2, 12, 0, Math.TAU);
ctx.fill();
ctx.beginPath();
ctx.lineWidth = 2;
ctx.arc(s2, s2, r, 0, Math.TAU);
ctx.moveTo(s2 + 12, s2);
ctx.arc(s2, s2, 12, 0, Math.TAU);
for (let a = 0; a < Math.TAU; a += Math.TAU / steps) {
const aa = a - Math.PI / steps;
ctx.moveTo(Math.cos(aa) * 12 + s2, Math.sin(aa) * 12 + s2);
ctx.lineTo(Math.cos(aa) * r + s2, Math.sin(aa) * r + s2);
}
//ctx.fill("evenodd");
ctx.stroke();
ctx.fillStyle = "#000";
ctx.font = "13px arial black";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
const tr = r - 8;
var idx = 1;
for (let a = 0; a < Math.TAU; a += Math.TAU / steps) {
const dx = Math.cos(a);
const dy = Math.sin(a);
ctx.setTransform(dy, -dx, dx, dy, dx * (tr - 4) + s2, dy * (tr - 4) + s2);
ctx.fillText(""+ (idx ++), 0, 0);
}
return ctx.canvas;
}
body { font-family: arial }

Categories

Resources