0

我在尝试使用画布实现“奖品轮”时遇到了麻烦。我正在使用类似于 在 StackOverflow 周围浮动的画布“轮盘赌” http://jsfiddle.net/wYhvB/4/的东西。我的困境是,当您单击旋转时,我在后台进行了一个 API 调用,该调用返回一个实际应该选择哪个奖品的 id,界面不过是养眼。我将所有奖品描述推送到第一个数组中,如何在每个弧中添加一个 id 并在特定的弧上停止而不是在特定的随机时间停止?IE 如果 API 返回“汽车”,我希望这个轮子旋转几次并停在汽车上。

    var colors = ["##eaeaea", "##cccccc", "##eaeaea", "##cccccc",
                  "##eaeaea", "##cccccc", "##eaeaea", "##cccccc"];
    // NEED to pre load this data prior
    var prize_descriptions = ["car","house","etc..."]; // These are injected on an init call from an api
    var current_user_status = {};

    var startAngle = 0;
    var arc = Math.PI / 4;
    var spinTimeout = null;

    var spinArcStart = 10;
    var spinTime = 0;
    var spinTimeTotal = 0;

    var current_user_status = null;
    var spin_results = null;

    var ctx;

    function drawSpinnerWheel() {
      var canvas = document.getElementById("canvas");
      if (canvas.getContext) {
        var outsideRadius = 200;
        var textRadius = 160;
        var insideRadius = 125;

        ctx = canvas.getContext("2d");
        ctx.clearRect(0,0,500,500);


        ctx.strokeStyle = "black";
        ctx.lineWidth = 2;

        ctx.font = 'bold 12px Helvetica, Arial';

        for(var i = 0; i < 8; i++) {
          var angle = startAngle + i * arc;
          ctx.fillStyle = colors[i];

          ctx.beginPath();
          ctx.arc(250, 250, outsideRadius, angle, angle + arc, false);
          ctx.arc(250, 250, insideRadius, angle + arc, angle, true);
          ctx.stroke();
          ctx.fill();

          ctx.save();
          ctx.shadowOffsetX = -1;
          ctx.shadowOffsetY = -1;
          ctx.shadowBlur    = 0;
          ctx.shadowColor   = "rgb(220,220,220)";
          ctx.fillStyle = "black";
          ctx.translate(250 + Math.cos(angle + arc / 2) * textRadius, 
                        250 + Math.sin(angle + arc / 2) * textRadius);
          ctx.rotate(angle + arc / 2 + Math.PI / 2);
          var text = prize_descriptions[i];
          if (text == undefined)
            text = "Not this time! "+i;
          ctx.fillText(text, -ctx.measureText(text).width / 2, 0);
          ctx.restore();
        } 

        //Arrow
        ctx.fillStyle = "black";
        ctx.beginPath();
        ctx.moveTo(250 - 4, 250 - (outsideRadius + 5));
        ctx.lineTo(250 + 4, 250 - (outsideRadius + 5));
        ctx.lineTo(250 + 4, 250 - (outsideRadius - 5));
        ctx.lineTo(250 + 9, 250 - (outsideRadius - 5));
        ctx.lineTo(250 + 0, 250 - (outsideRadius - 13));
        ctx.lineTo(250 - 9, 250 - (outsideRadius - 5));
        ctx.lineTo(250 - 4, 250 - (outsideRadius - 5));
        ctx.lineTo(250 - 4, 250 - (outsideRadius + 5));
        ctx.fill();
      }
    }

    function spin() {   
      spinAngleStart = Math.random() * 10 + 10;
      spinTime = 0;
      spinTimeTotal = Math.random() * 3 + 4 * 1000;
      rotateWheel();
    }

    function rotateWheel() {
      spinTime += 30;
      if(spinTime >= spinTimeTotal) {
        stopRotateWheel();
        return;
      }
      var spinAngle = spinAngleStart - easeOut(spinTime, 0, spinAngleStart, spinTimeTotal);
      startAngle += (spinAngle * Math.PI / 180);
      drawSpinnerWheel();
      spinTimeout = setTimeout('rotateWheel()', 30);
    }

    function stopRotateWheel() {
      clearTimeout(spinTimeout);
      var degrees = startAngle * 180 / Math.PI + 90;
      var arcd = arc * 180 / Math.PI;
      var index = Math.floor((360 - degrees % 360) / arcd);
      ctx.save();
      ctx.font = 'bold 30px Helvetica, Arial';
      var text = prize_descriptions[index];
      ctx.fillText(text, 250 - ctx.measureText(text).width / 2, 250 + 10);
      ctx.restore();
    }

    function easeOut(t, b, c, d) {
      var ts = (t/=d)*t;
      var tc = ts*t;
      return b+c*(tc + -3*ts + 3*t);
    }


drawSpinnerWheel();

$("#spin").bind('click', function(e) {
  e.preventDefault();
  spin();
});​
4

2 回答 2

4

I have some experience in creating HTML5 canvas winning wheels, the way that I solved the problem of getting the wheel to stop at at a particular prize determined by a server-side process was to define an array of prizes that correspond to what is shown on the wheel, specifying the start and end angle of each prize in degrees, and then before the wheel is rotated setting a targetAngle to a random value between the start and end angle of the prize in question plus adding a multiple of 360 degrees so that the wheel spins a few times before slowing to a stop at the predetermined prize.

For example if the wheel has 4 prizes then the prizes array is...

var prizes = new Array();
prizes[0] = {"name" : "Prize 1", "startAngle" : 0,   "endAngle" : 89};
prizes[1] = {"name" : "Prize 2", "startAngle" : 90,  "endAngle" : 179};
prizes[2] = {"name" : "Prize 3", "startAngle" : 180, "endAngle" : 269};
prizes[3] = {"name" : "Prize 4", "startAngle" : 270, "endAngle" : 359};

And the code to set the targetAngle is something like...

targetAngle = Math.floor(prizes[determinedPrize]['startAngle'] + (Math.random() * (prizes[determinedPrize]['endAngle'] - prizes[determinedPrize]['startAngle'])));
targetAngle += (360 * 18);

The spinning function of the wheel then loops until the current angle of the wheel equals the targetAngle.

A working example of my prize wheel and the fully commented source code is available at http://www.dougtesting.net. The pre-determined feature is not enabled in the online example, but can easily be turned on in the source code (winwheel.js) once downloaded.

于 2013-07-18T10:14:59.990 回答
1

我能想到的最简单的方法是取轮子的当前位置,然后计算从这个点到奖品的距离。添加轮径周长的随机倍数,然后你就有一个距离。轮子的边缘必须经过这个距离才能获得奖品。

就像您可以使用线性或三次插值以指定的步数将元素从 1 个位置移动到另一个位置一样,您可以使用相同的方法将轮子从点 0(起点)旋转到点 1(终点)时间=0 到时间=1

本页数学:缓入,缓出使用带有时间约束的 Hermite 曲线的位移是一本不错的读物。这是我设法绕着头做基本相同的事情的地方——只是上/下/左/右,而不是旋转。

我刚才看 iot 时有点波涛汹涌。不知道是 jsfiddle、丢失的图像还是我正在运行的 25 个浏览器选项卡和程序。无论如何,关键是使用非线性插值以指定的步数到达指定的距离。它应该在指定的时间到达那里,但不是在打开 25 个窗口的情况下.. :laughs:

查看上面的 SO 链接。它有一些很棒的图片,可以很好地解释。

这是当时的三次样条插值。 http://jsfiddle.net/enhzflep/XKzGF/

这是完整的代码:

<!DOCTYPE html>
<html>
<head>
<script>
var continuePlaying = true, isPlaying=false;

function byId(a){return document.getElementById(a)}
function myInit()
{
}
window.addEventListener("load",myInit,!1);

function cubicHermite(a,b,d,e,c){var g=a*a,f=g*a;return(2*f-3*g+1)*b+(f-2*g+a)*e+(-2*f+3*g)*d+(f-g)*c}
function interp(a,b,d,e,c){var g,f;f=e/(a/2+b+d/2);g=f*a/2;f*=b;return result=c<=a?cubicHermite(c/a,0,g,0,f/b*a):c<=a+b?g+f*(c-a)/b:cubicHermite((c-a-b)/d,g+f,e,f/b*d,0)}
function linear(a){return a}
function cubic(a){return interp(0.35,0.3,0.35,1,a)}
function getSize(a){return{left:a.offsetLeft,top:a.offsetTop,width:a.clientWidth,height:a.clientHeight}}
function doAnim2(a,b,d,e){var c=a/b;setTimeout(function(){doAnimStep(0,b,c,d,e)},c)}
function doAnimStep(a,b,d,e,c){a<=b?(setTimeout(function(){doAnimStep(a,b,d,e,c)},d),e(a/b),a++):void 0!=c&&null!=c&&c()}


//scroll with cubic interpolation of the current scroll position
function cubicScrollDown(b,callback)
{
    var a=byId(b),c=a.scrollHeight-a.clientHeight;
    doAnim2(500,c,function(b){a.scrollTop=c*cubic(b)},callback);
}
function cubicScrollUp(b,callback)
{
    var a=byId(b),c=a.scrollHeight-a.clientHeight;
    doAnim2(500,c,function(b){ a.scrollTop=c*(1-cubic(b)) },callback );
}

//scroll with cubic interpolation of the current scroll position
function linearScrollDown(b, callback)
{
    var a=byId(b),c=a.scrollHeight-a.clientHeight;
    doAnim2(500,c,function(b){a.scrollTop=c*linear(b)}, callback);
}
function linearScrollUp(b, callback)
{
    var a=byId(b),c=a.scrollHeight-a.clientHeight;
    doAnim2(1000,c,function(b){ a.scrollTop=c*(1-linear(b)) }, callback );
}

function animFadeOut(elem, callback)
{
    doAnim2(500,50,function(raw){elem.style.opacity=1-cubic(raw)},callback);
}

function animFadeIn(elem, callback)
{
    doAnim2(500,50,function(raw){elem.style.opacity=cubic(raw)},callback);
}


function cubicBounce(b)
{
    cubicScrollDown(b, downCallback);

    function downCallback()
    {
        cubicScrollUp(b, upCallback);
    }

    function upCallback()
    {
        if (continuePlaying===true)
            setTimeout( function(){cubicBounce(b);}, 0);
        else
            continuePlaying = true;
    }
}

function linearBounce(b)
{
    linearScrollDown(b, downCallback);

    function downCallback()
    {
        linearScrollUp(b, upCallback);
    }

    function upCallback()
    {
        if (continuePlaying===true)
            setTimeout( function(){linearBounce(b);}, 0);
        else
            continuePlaying = true;
    }
}

function fadeOutIn(tgtListIdStr)
{
    var tgt = byId(tgtListIdStr);
    
    animFadeOut(tgt,fadedOutCallback);
    
    function fadedOutCallback()
    {
        animFadeIn(tgt);
    }
}

function prependChild(parent, element)
{
    if (parent.childNodes)
        parent.insertBefore(element, parent.childNodes[0]);
    else
        parent.appendChild(element)
}



function slideUpRemove(tgtListIdStr)
{
    var tgt = byId(tgtListIdStr);
    var listItems = tgt.getElementsByTagName('li');
    mHeight = listItems[0].clientHeight;
    
    animFadeOut(listItems[0], slideUp);

    function slideUp()
    {
        doAnim2(500, 50, slideUpStep, slideUpDone);
        
        function slideUpStep(raw)
        {
            listItems[0].style.height = (cubic(1-raw) * mHeight) + 'px';
        }
        
        function slideUpDone()
        {
            dummy = listItems[0];
            tgt.appendChild(dummy);
            //dummy.removeAttribute('style');
            dummy.style.height = null;
            dummy.style.opacity = null;
        }
    }
}



function slideDownInsert(tgtListIdStr)
{
    // get the container, it's items and the height of the last LI item.
    var tgt = byId(tgtListIdStr);
    var listItems = tgt.getElementsByTagName('li');
    mHeight = listItems[listItems.length-1].clientHeight;
    
    // create a dummy to take the place of the last item, set it's size and height. make it the first child of the containing list
    var dummy = document.createElement('li');
    dummy.style.opacity = 0;
    dummy.style.height = 0 + 'px';
    prependChild(tgt, dummy);
    
    // animate it!
    doAnim2(500, 50, slideDownStep,slideDownDone);
    
    function slideDownStep(raw)
    {
        dummy.style.height = (cubic(raw) * mHeight)+'px';
    }
    
    function slideDownDone()
    {
        // remove the dummy
        var newItem = listItems[listItems.length-1];
        newItem.style.opacity = 0;
        prependChild(tgt, newItem);
        tgt.removeChild(dummy);
        animFadeIn(newItem, function(){newItem.removeAttribute('style')});
    }
}
</script>
<style>
#myListDiv
{
    width: 256px;
    padding: 6px;
    height: 128px;
    overflow-y: hidden; /*scroll;*/
    border-radius: 6px;
    border: solid 1px transparent;
    border-color: rgba(0,0,0,0.2) rgba(255,255,255,0.4) rgba(255,255,255,0.4) rgba(0,0,0,0.2);
/*  background-image: url(img/rss128.png);  */
    background-color: rgba(0,0,0,0.1);
}
h4, p
{
    margin: 6px 0;
}

ul 
{
    padding: 0;
    list-style: none;
    margin: 0;
}

ul#mList li
{
    padding: 0 8px;
    margin: 0 6px;
    display: block;
    border: solid 1px #cccccc;
    border-bottom-color: #000;
    border-color: #ccc transparent #000 transparent;
    vertical-align: middle;
    background-color: rgba(150,150,150,0.95);
    overflow: hidden;
}
.thumb
{
    width: 48px;
    height: 48px;
    float: left; 
}
.thumb img
{
    height: 48px;
}
#mPanel
{
    display: inline-block;
    float: left;
    padding: 32px;
    background-color: hsl(80,50%,20%);
}
</style>
</head>
<body>
    <div id='mPanel'>
        <div id='myListDiv'>
            <ul id='mList'>
                <li><div class='thumb'><img src='img/opera.svg'></div><div class='itemData'><h4><a>Item #1</a></h4><p>some assorted text</p></div></li>
                <li><div class='thumb'><img src='img/chromeEyes.svg'></div><h4><a>Item #2</a></h4><p>some assorted text</p></li>
                <li><div class='thumb'><img src='img/girl.png'></div><h4><a>Item #3</a></h4><p>some assorted text</p></li>          
                <li><div class='thumb'><img src='img/chuck-norris.jpg'></div><h4><a>Item #1</a></h4><p>some assorted text</p></li>          
                <li><div class='thumb'><img src='img/redBaron.jpg'></div><h4><a>Item #2</a></h4><p>some assorted text</p></li>
                <li><div class='thumb'><img src='img/default.png'></div><h4><a>Item #3</a></h4><p>some assorted text</p></li>
            </ul>
        </div>
    </div>
    
    <button onclick='cubicScrollDown("myListDiv")'>Cubic down</button>
    <button onclick='cubicScrollUp("myListDiv")'>Cubic up</button><br>
    <button onclick='cubicBounce("myListDiv")'>cubic bounce</button>
    <button onclick='linearBounce("myListDiv")'>linear bounce</button><br>
    
    <input type='button' onclick='slideUpRemove("mList")' value='newest'/>
    <input type='button' onclick='slideDownInsert("mList")' value='Slide Down'/><br>
    <button onclick='continuePlaying=false'>Stop Anim cycle</button>
    <input type='button' onclick='fadeOutIn("mList");' value='fadeOutIn'/><br>
</body>
</html>
于 2012-10-29T23:47:32.453 回答