我们如何才能在旋转后的准确点停止这个 HTML5 画布轮?
Posted
技术标签:
【中文标题】我们如何才能在旋转后的准确点停止这个 HTML5 画布轮?【英文标题】:How can we stop this HTML5 Canvas wheel at exact points after spin? 【发布时间】:2020-04-04 17:59:36 【问题描述】:在下面的代码链接中,html5 画布旋转轮游戏。我想将此画布停止在用户定义的位置,就好像用户希望始终停止在 200 个文本或 100 个这样的文本处。
目前,它停在随机点上
我们怎样才能做到这一点???谁能帮忙!!!!!!
还附上 Codepen 链接。
HTML 文件
<div>
<canvas class="spin-wheel" id="canvas" ></canvas>
</div>
JS 文件
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
【问题讨论】:
要让您了解如何在一定程度上停止,请参阅this answer 【参考方案1】:缓和曲线
如果您在车轮减速到停止时随时间绘制车轮位置,您会看到一条曲线,一条看起来像半抛物线的曲线。
如果你将 x 的平方值绘制在 0 到 1 的范围内,就像在下一个 sn-p 中一样,你可以获得完全相同的曲线,红线显示 f(x) => x * x
的图,其中 0 <= x <= 1
不幸的是,情节是错误的,需要在 x 和 y 中进行镜像。这很简单,只需将函数更改为f(x) => 1 - (1 - x) ** 2
(单击画布即可获得黄线)
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();
在动画中,这条曲线是一种缓动。
我们可以创建使用缓动函数的函数,计算时间并返回***的位置。我们可以提供一些附加值来控制车轮停止所需的时间、起始位置和所有重要的停止位置。
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;
演示
演示将其付诸实践。演示中的函数没有使用平方根,而是将根定义为常量slowDownRate = 2.6
。该值越小,启动速度越大,结束速度越慢。值为 1 表示它将以恒定速度移动然后停止。该值必须 > 0 且
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
【讨论】:
如何阻止车轮再次旋转?我试图从按钮单击开始动画。一旦停止,是否可以从停止的位置开始? @Mark 我对演示(答案底部)进行了疯狂的更改,可以满足您的需求。以上是关于我们如何才能在旋转后的准确点停止这个 HTML5 画布轮?的主要内容,如果未能解决你的问题,请参考以下文章