炫酷的canvas续篇

通过第一篇,已经实现了用canvas来绘制一个计时器,接下来我们要让它动起来,给它增加动画效果。只要有计时器上有数字改变,就产生小球掉落效果,且颜色随机,方向为前后随机,集体代码如下

首先我们要在全局定义一个小球数组变量和一个小球颜色常量数组

var balls=[];
const colors=["#33b5e5","#0099cc","#aa66cc","#9933cc","#99cc00","#669900","#ffbb33","#ff8800","#ff4444","#cc0000"];
定义好之后,我们进行掉落小球的绘制。每一个小球对象,除了有球心的x、y坐标外,为了模拟现实中的场景,还要给它一个重力加速度,以及x、y轴方向的速度,以及颜色属性,每个小球对象定义好之后,将小球添加到小球数组中,代码如下:

function addBalls(x,y,num){
    for(var i=0;i<digit[num].length;i++){
        for(var j=0;j<digit[num][i].length;j++){
            if(digit[num][i][j]==1){
                //产生一个小球对象
                var aBall={
                    x:x+j*2*(RADIUS+1)+(RADIUS+1),
                    y:y+i*2*(RADIUS+1)+(RADIUS+1),
                    g:1.5+Math.random(),
                    vx:Math.pow(-1,Math.ceil(Math.random()*1000))*4,   
                    vy:-5,
                    color:colors[parseInt(Math.random()*colors.length)]
                }
                balls.push(aBall);
            }
        }
    }
}
上述代码中,vx在-4和4中随机选取,颜色也是在colors数组中随机取。addBalls函数接收三个参数,依次为小球组成数字的起始x坐标、y坐标以及该数字(或冒号)。

定义好创建掉落小球的方法后,就可以在时间变化的时候调用该函数了,即nextSeconds!=curSeconds的时候,这里我们应该在update()函数里,代码如下:

//如果小时的十位数发生改变,就产生该小时十位数对应的小球数个小球
if(parseInt(curHours/10)!=parseInt(nextHours/10)){
    addBalls(MARGIN_LEFT+0,MARGIN_TOP,parseInt(curHours/10));
}
//如果小时的个位数发生改变,就产生该小时个位数对应的小球数个小球
if(parseInt(curHours%10)!=parseInt(nextHours%10)){
    addBalls(MARGIN_LEFT+15*(RADIUS+1),MARGIN_TOP,parseInt(curHours%10));
}
if(parseInt(curMinutes/10)!=parseInt(nextMinutes/10)){
    addBalls(MARGIN_LEFT+39*(RADIUS+1),MARGIN_TOP,parseInt(curMinutes/10));
}
if(parseInt(curMinutes%10)!=parseInt(nextMinutes%10)){
    addBalls(MARGIN_LEFT+54*(RADIUS+1),MARGIN_TOP,parseInt(curMinutes%10));
}
if(parseInt(curSeconds/10)!=parseInt(nextSeconds/10)){
    addBalls(MARGIN_LEFT+78*(RADIUS+1),MARGIN_TOP,parseInt(curSeconds/10));
}
if(parseInt(curSeconds%10)!=parseInt(nextSeconds%10)){
    addBalls(MARGIN_LEFT+93*(RADIUS+1),MARGIN_TOP,parseInt(curSeconds%10));
}
这里我们需要注意一下每个数字起始的x坐标的计算,仔细推算一下即可

掉落的小球创建了之后,就需要往canvas画布上绘制了,我们在负责绘制的render()方法里面加入如下代码:

//绘制掉落的小球
for(var i=0;i<balls.length;i++){
    cxt.fillStyle=balls[i].color;
    cxt.beginPath();
    cxt.arc(balls[i].x,balls[i].y,RADIUS,0,Math.PI*2);
    cxt.closePath();
    cxt.fill();
}
上面的代码中,我们先循环变量掉落的小球数组,然后进行小球的绘制。绘制出掉落小球后,我们还需要让它们动起来才能有动画效果,创建一个updateBalls()方法,负责小球的移动,代码如下:

function updateBalls(){
    for(var i=0;i<balls.length;i++){
        balls[i].x+=balls[i].vx;
        balls[i].y+=balls[i].vy;
        balls[i].vy+=balls[i].g;
        //碰撞检测,如果当前小球碰到地面,让小球弹起
        if(balls[i].y>=WINDOW_HEIGHT-RADIUS){
            balls[i].y=WINDOW_HEIGHT-RADIUS;
            balls[i].vy=-balls[i].vy*0.75;
        }
    }
}
定义好updateBalls方法后,我们在负责界面更新的update方法里面调用updateBalls方法;

至此,我们就完成了掉落小球的动画效果,截图如下:


大体上我们已经完成了这个炫酷的canvas计时器,不过还有一些需要优化的地方,我们会发现这些掉落的小球一直在增加,balls数组的长度也会一直增加,而由于我们的家算计内存有限,这样的程序是不可能一直运行的,为此我们需要进行优化。

对于移动出屏幕的小球,我们可以将它们弹出balls数组,这样balls数组内的小球都是在屏幕以内的,保证占用的内存很少,这样的程序是可以持续运行的。我们在updateBalls方法里面进行优化,代码如下:

var cnt=0;                       //记录还有多少个小球保留在画面中
for(var i=0;i<balls.length;i++){
    if(balls[i].x+RADIUS>0&&balls[i].x-RADIUS<WINDOW_WIDTH){
        //将在屏幕内的小球放在数组前面,这样balls[cnt-1]后面的小球都是可以删掉的
        balls[cnt++]=balls[i];
    }
}
//将移出屏幕的小球删掉
while(balls.length>Math.min(300,cnt)){     //屏幕中小球的个数为300和cnt中的最小值
    balls.pop();
}
这样,我们的功能就全部完成了,还可以根据自己的需要进行修改。通过这个项目,对canvas应用进行了温习,又得到了很多乐趣,这个小demo,感觉还是挺好玩的,对自己js写动画也有很大的帮助,对面向对象也有了进一步的理解。下面贴出完整的js代码:

var WINDOW_WIDTH=1024;
var WINDOW_HEIGHT=600;
var RADIUS=8;             //小球的半径
var MARGIN_TOP=60;        //每一个数字距离画布顶端的距离
var MARGIN_LEFT=30;       //第一个数字距离画布左边距的距离
var endTime=new Date();
endTime.setTime(endTime.getTime()+3600*1000);    //倒计时的截止时间,为1小时之后
var curShowTimeSeconds=0;
var balls=[];
const colors=["#33b5e5","#0099cc","#aa66cc","#9933cc","#99cc00","#669900","#ffbb33","#ff8800","#ff4444","#cc0000"];
window.onload=function(){
    var canvas=document.getElementById("canvas");
    var context=canvas.getContext("2d");
    canvas.width=WINDOW_WIDTH;
    canvas.height=WINDOW_WIDTH;
    curShowTimeSeconds=getCurrentShowTimeSeconds();
    setInterval(function(){
        render(context);
        update();
    },50);
}
function update(){
    //更新显示的时间,产生时间变化的动画
    var nextShowTimeSeconds=getCurrentShowTimeSeconds();
    var nextHours=parseInt(nextShowTimeSeconds/3600);
    var nextMinutes=parseInt((nextShowTimeSeconds-nextHours*3600)/60);
    var nextSeconds=nextShowTimeSeconds%60;

    var curHours=parseInt(curShowTimeSeconds/3600);
    var curMinutes=parseInt((curShowTimeSeconds-curHours*3600)/60);
    var curSeconds=curShowTimeSeconds%60;
    //只比较秒即可,因为每50毫秒就比较一次
    if(nextSeconds!=curSeconds){
        //如果小时的十位数发生改变,就产生该小时十位数对应的小球数个小球
        if(parseInt(curHours/10)!=parseInt(nextHours/10)){
            addBalls(MARGIN_LEFT+0,MARGIN_TOP,parseInt(curHours/10));
        }
        //如果小时的个位数发生改变,就产生该小时个位数对应的小球数个小球
        if(parseInt(curHours%10)!=parseInt(nextHours%10)){
            addBalls(MARGIN_LEFT+15*(RADIUS+1),MARGIN_TOP,parseInt(curHours%10));
        }
        if(parseInt(curMinutes/10)!=parseInt(nextMinutes/10)){
            addBalls(MARGIN_LEFT+39*(RADIUS+1),MARGIN_TOP,parseInt(curMinutes/10));
        }
        if(parseInt(curMinutes%10)!=parseInt(nextMinutes%10)){
            addBalls(MARGIN_LEFT+54*(RADIUS+1),MARGIN_TOP,parseInt(curMinutes%10));
        }
        if(parseInt(curSeconds/10)!=parseInt(nextSeconds/10)){
            addBalls(MARGIN_LEFT+78*(RADIUS+1),MARGIN_TOP,parseInt(curSeconds/10));
        }
        if(parseInt(curSeconds%10)!=parseInt(nextSeconds%10)){
            addBalls(MARGIN_LEFT+93*(RADIUS+1),MARGIN_TOP,parseInt(curSeconds%10));
        }
        curShowTimeSeconds=nextShowTimeSeconds;
    }
    //更新掉落小球的函数
    updateBalls();
}
function updateBalls(){
    for(var i=0;i<balls.length;i++){
        balls[i].x+=balls[i].vx;
        balls[i].y+=balls[i].vy;
        balls[i].vy+=balls[i].g;
        //碰撞检测,如果当前小球碰到地面,让小球弹起
        if(balls[i].y>=WINDOW_HEIGHT-RADIUS){
            balls[i].y=WINDOW_HEIGHT-RADIUS;
            balls[i].vy=-balls[i].vy*0.75;
        }
    }

    //下面几行代码是对性能进行优化
    //对移出屏幕的小球进行删除操作,否则balls里面的小球会一直增加
    var cnt=0;          //记录还有多少个小球保留在画面中
    for(var i=0;i<balls.length;i++){
        if(balls[i].x+RADIUS>0&&balls[i].x-RADIUS<WINDOW_WIDTH){
            //将在屏幕内的小球放在数组前面,这样balls[cnt-1]后面的小球都是可以删掉的
            balls[cnt++]=balls[i];
        }
    }
    //将移出屏幕的小球删掉
    while(balls.length>Math.min(300,cnt)){     //屏幕中小球的个数为300和cnt中的最小值
        balls.pop();
    }
}

function addBalls(x,y,num){
    for(var i=0;i<digit[num].length;i++){
        for(var j=0;j<digit[num][i].length;j++){
            if(digit[num][i][j]==1){
                //产生一个小球对象
                var aBall={
                    x:x+j*2*(RADIUS+1)+(RADIUS+1),
                    y:y+i*2*(RADIUS+1)+(RADIUS+1),
                    g:1.5+Math.random(),              //小球的重力加速度
                    vx:Math.pow(-1,Math.ceil(Math.random()*1000))*4,   //使小球x方向正负移动为随机
                    vy:-5,
                    color:colors[parseInt(Math.random()*colors.length)]
                }
                balls.push(aBall);
            }
        }
    }
}

function getCurrentShowTimeSeconds(){
    var curTime=new Date();
    var ret=endTime.getTime()-curTime.getTime();
    ret=Math.round(ret/1000);
    return ret>=0?ret:0;
}
function render(cxt){
    //每一次更新画面,都要先清除画布,否则会叠加
    cxt.clearRect(0,0,WINDOW_WIDTH,WINDOW_HEIGHT);
    var hours=parseInt(curShowTimeSeconds/3600);
    var minutes=parseInt((curShowTimeSeconds-hours*3600)/60);
    var seconds=curShowTimeSeconds%60;
    //绘制数字的函数
    renderDigit(MARGIN_LEFT,MARGIN_TOP,parseInt(hours/10),cxt);                          //绘制小时得第一个数字
    renderDigit(MARGIN_LEFT+15*(RADIUS+1),MARGIN_TOP,parseInt(hours%10),cxt);            //绘制小时的第二个数字,14*(RADIUS+1)为一个数字的宽度
    renderDigit(MARGIN_LEFT+30*(RADIUS+1),MARGIN_TOP,10,cxt);                            //绘制冒号
    renderDigit(MARGIN_LEFT+39*(RADIUS+1),MARGIN_TOP,parseInt(minutes/10),cxt);          //绘制分钟的第一个数字
    renderDigit(MARGIN_LEFT+54*(RADIUS+1),MARGIN_TOP,parseInt(minutes%10),cxt);          //绘制分钟的第二个数字
    renderDigit(MARGIN_LEFT+69*(RADIUS+1),MARGIN_TOP,10,cxt);                            //绘制冒号
    renderDigit(MARGIN_LEFT+78*(RADIUS+1),MARGIN_TOP,parseInt(seconds/10),cxt);          //绘制秒钟的第一个数字
    renderDigit(MARGIN_LEFT+93*(RADIUS+1),MARGIN_TOP,parseInt(seconds%10),cxt);         //绘制秒钟的第二个数字

    //绘制掉落的小球
    for(var i=0;i<balls.length;i++){
        cxt.fillStyle=balls[i].color;
        cxt.beginPath();
        cxt.arc(balls[i].x,balls[i].y,RADIUS,0,Math.PI*2);
        cxt.closePath();
        cxt.fill();
    }
}
function renderDigit(x,y,num,cxt){  //参数分别为绘制起始位置,绘制的数字,context对象
    cxt.fillStyle="rgb(0,102,153)";
    for(var i=0;i<digit[num].length;i++){
        for(var j=0;j<digit[num][i].length;j++){
            if(digit[num][i][j]==1){
                cxt.beginPath();
                cxt.arc(x+j*2*(RADIUS+1)+(RADIUS+1),y+i*2*(RADIUS+1)+(RADIUS+1),RADIUS,0,2*Math.PI);
                cxt.closePath();
                cxt.fill();
            }
        }
    }
}


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值