我们如何才能在旋转后的准确点停止这个 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) =&gt; x * x 的图,其中 0 &lt;= x &lt;= 1

不幸的是,情节是错误的,需要在 x 和 y 中进行镜像。这很简单,只需将函数更改为f(x) =&gt; 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 画布轮?的主要内容,如果未能解决你的问题,请参考以下文章

如何判断一个点在旋转后的矩形中

如何在 HTML5 音频中捕捉“停止”事件然后做点啥?

图像旋转的原理

图像旋转算法与实现

html5 threejs webgl 双击放大

如何使用 GLM 在 OpenGL 中正确计算旋转