HTML5 Canvas& javascript奖轮

时间:2012-10-29 21:00:48

标签: javascript css html5 animation canvas

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

    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();
});​

2 个答案:

答案 0 :(得分:4)

我在创建HTML5画布获胜轮子方面有一些经验,我解决了通过服务器端流程确定的特定奖项让车轮停下来的问题的方法是定义一系列与之相对应的奖品在轮子上显示,以度为单位指定每个奖品的开始和结束角度,然后在轮子旋转之前将targetAngle设置为所讨论的奖品的开始和结束角度之间的随机值,再加上360度的倍数这样轮子在减速到停止预定奖赏之前会旋转几次。

例如,如果车轮有4个奖品,则奖品数组为......

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};

设置targetAngle的代码就像......

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

然后车轮的旋转功能循环,直到车轮的当前角度等于targetAngle。

我的奖励轮的工作示例和完整评论的源代码可在http://www.dougtesting.net获得。在线示例中未启用预定功能,但下载后可以在源代码(winwheel.js)中轻松打开。

答案 1 :(得分:1)

我能想到的最简单的方法是获取方向盘的当前位置,然后计算从这一点到奖品的距离。添加一个随机数的轮<打击>直径圆周的倍数,然后你有一个距离。车轮的边缘必须穿过这个距离才能获得奖品。

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

此页面Math: Ease In, ease Out a displacement using Hermite curve with time constraint读起来不错。在这里,我设法绕着基本相同的东西 - 只是向上/向下/向左/向右,而不是旋转。

我现在看看iot有点不稳定。不知道它是jsfiddle,缺少图像还是25个浏览器标签&amp;我运行的程序。 无论如何,关键是使用非线性插值以指定的步数到达指定的距离。它应该在指定的时间到达那里,但不会打开25个窗口......:笑:

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

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

这是完整的代码,因为提交 - wizzard不喜欢我的帖子。 说:“哎呀!你的答案无法提交,因为: 请通过在线包含相关的格式化代码来完成您的答案(不要只链接到jsfiddle.net)。“

<!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>