通过第一篇,已经实现了用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(); } } } }