先来个效果图:
效果很不错吧,学习视频链接如下:http://www.imooc.com/learn/133。
(PS:老师讲的太好,忍不住为他打个小广告了,,强烈推荐去学习啊)
1、页面结构:跟前面两个一样的,body中一个canvas标签,id为myCanvas。另外引入digit.js和countdown.js。
2、digit.js:定义了一个名为digit的全局数组,记录了0-9以及冒号的11个点阵,一种1代表该处需要绘制一个小球,0处表示该处为空,不需要绘制东西。以下为0的点阵信息:
[ [0,0,1,1,1,0,0], [0,1,1,0,1,1,0], [1,1,0,0,0,1,1], [1,1,0,0,0,1,1], [1,1,0,0,0,1,1], [1,1,0,0,0,1,1], [1,1,0,0,0,1,1], [1,1,0,0,0,1,1], [0,1,1,0,1,1,0], [0,0,1,1,1,0,0] ],//0
数组的下标从0开始,所以 digit数组也是从0开始,这样子digit[0]取的也就是0的点阵信息,digit[1]即为数字1的点阵信息,是不是很巧妙。
3、countdown.js:用来获取当前时间,根据当前时间和digit.js中的点阵信息以小球的形式显示电子钟,再根据时间的变化绘制上弹跳小球。代码如下:
var WINDOW_Width=1024;//canvas的宽度 var WINDOW_HEIGHT=600;//canvas的高度 var RADIUS=8;//圆球半径 var MAGIN_TOP=60;//每个数字距离画布上部的距离 var MAGIN_LEFT=30;//每个数字距离画布左部的距离 //var endTime = new Date(2016,5,30,18,00,00);//指定结束时间 //指定时间为当前时间一个小时之后 var endTime = new Date(); endTime.setTime(endTime.getTime()+1*60*60*1000); var interval=null; var curShowTimeSeconds=0; //添加弹跳小球数据 var ball=[];//小球的坐标 var colors=["#33B5E5","#0099CC","#AA66CC","#9933CC","#99CC00","#669900","#FFBB33","#FF8800","#FF4444","#CC0000"];//小球的颜色 window.οnlοad=function(){ //1、获取myCanvas对象 var myCanvas = document.getElementById("myCanvas"); //判断当前浏览器是否支持canvas if(myCanvas.getContext("2d")){ var context = myCanvas.getContext("2d");//获取绘图上下文环境 //通过获取屏幕的宽度和高度,给canvas设置宽高以自适应屏幕 //此处有三个注意点: //1、必须给body和canvas标签添加style="height:100%;"的样式,让时间数字铺开整个屏幕; //2、必须使用document.documentElement.clientHeight,若是使用document.body.clientHeight获取到的高度非常小,显示的数字只能显示一半 //3、必须减去20,否则会出现滚动条 WINDOW_Width = document.documentElement.clientWidth-20; WINDOW_HEIGHT = document.documentElement.clientHeight-20; //设置宽高 myCanvas.width=WINDOW_Width; myCanvas.height=WINDOW_HEIGHT; //设置margin-left和margin-top,以及RADIUS //10:时间数字占据整个屏幕的4/5,那么两边总共是1/5,一侧则是1/10 MAGIN_LEFT = Math.round(WINDOW_Width/10); //时间数字占据整个屏幕的4/5,即window_width*4/5,这是所有时间数字占据的屏幕总宽度 //而最后一个数字的左边距为margin_left+93*(radius+1),在加上它本身的15个(radius+1),因此这个4/5的屏幕宽度总共放置了93+15=108个(radius+1),计算出每个(radius+1)之后再减去1就是radius的值了 RADIUS = Math.round(WINDOW_Width*4/5/108)-1;//108=93+15 //margin_top跟上下文的坐标的计算没有关系,因此可以随便定义,此处定义为屏幕高度的1/5 MAGIN_TOP = Math.round(WINDOW_HEIGHT/5); //alert(WINDOW_Width+"\n"+WINDOW_HEIGHT); //2、绘制当前时间图形 curShowTimeSeconds = getCurrentShowTimeSeconds(); setInterval(function(){ render(context);//绘制数字和小球 update();//更新数字和小球信息 },50);//50毫秒执行一次 }else{ alert("当前浏览器不支持canvas标签"); } } /** *该方法用来绘制图形 *@param cxt:上下文绘图环境context **/ function render(cxt){ if(curShowTimeSeconds>0){ cxt.clearRect(0,0,WINDOW_Width,WINDOW_HEIGHT);//刷新整个canvas,不然时间数字和弹跳小球会堆积在整个画布中 var hour = parseInt((curShowTimeSeconds/(60*60))%24,10);//小时 var minute = parseInt((curShowTimeSeconds/60)%60,10);//分钟:先计算成总共有多少分钟,再取分钟的余数 var seconds = curShowTimeSeconds%60;//秒:本来就是秒,直接获取最后剩下的秒就好了 //补零:当分钟和秒数只有两位数时,十位数/10时结果为零,会自动绘制0的图形,所以不再需要手动补零 //minute = checkTime(minute); //seconds = checkTime(seconds); //时+冒号 renderDigit(MAGIN_LEFT,MAGIN_TOP,parseInt(hour/10,10),cxt);//小时的十位数 //15=7*2+1,7:每个点阵是7*10,每个方形的直径为2*(RADIUS+1),所以要乘以2,最后的1是保留空间,让两个数字之间有间距 renderDigit(MAGIN_LEFT+(15*(RADIUS+1)),MAGIN_TOP,parseInt(hour%10,10),cxt);//小时的个位数 //30:前面一个已经到15*(RADIUS+1),且每一个数字的间距都是15*(RADIUS+1),因此此处就到30了 //10:digit点阵下标为10的,刚好是冒号对应的点阵,所以用10 renderDigit(MAGIN_LEFT+(30*(RADIUS+1)),MAGIN_TOP,10,cxt);//小时后面的冒号 //分+冒号 //39=30+4*2+1:30是前一个冒号的x坐标,4是“冒号的点阵为4*10”,2为“方形的直径为2*(RADIUS+1)”,1为保留空间 renderDigit(MAGIN_LEFT+(39*(RADIUS+1)),MAGIN_TOP,parseInt(minute/10,10),cxt);//分钟的十位数 //54=39+15 renderDigit(MAGIN_LEFT+(54*(RADIUS+1)),MAGIN_TOP,parseInt(minute%10,10),cxt);//分钟的个位数 //69=54+15 renderDigit(MAGIN_LEFT+(69*(RADIUS+1)),MAGIN_TOP,10,cxt);//分钟后面的冒号 //秒+冒号 //78=69+4*2+1 renderDigit(MAGIN_LEFT+(78*(RADIUS+1)),MAGIN_TOP,parseInt(seconds/10,10),cxt);//秒数的十位数 //93=78+15 renderDigit(MAGIN_LEFT+(93*(RADIUS+1)),MAGIN_TOP,parseInt(seconds%10,10),cxt);//秒数的十位数 //绘制小球 for(var i=0;i<ball.length;i++){ cxt.fillStyle=ball[i].color;//填充颜色 cxt.beginPath(); cxt.arc(ball[i].x,ball[i].y,RADIUS,0,2*Math.PI,true);//绘制弹跳小球 cxt.closePath(); cxt.fill();//给小球填充颜色 } } } //获取当前时间 function getCurrentShowTimeSeconds(){ /*(倒计时) var startTime = new Date().getTime();//开始时间 var diffTime = parseInt((endTime-startTime)/1000,10);//得到两个时间差的秒数 return diffTime>0?diffTime:0; */ //时钟效果 var curTime = new Date();//开始时间 var diffTime = curTime.getHours()*60*60+curTime.getMinutes()*60+curTime.getSeconds();//得到两个时间差的秒数(电子钟报时) return diffTime; } function update(){ var nextShowTimeSeconds = getCurrentShowTimeSeconds(); var nextHour = parseInt((nextShowTimeSeconds/(60*60))%24,10);//小时 var nextMinute = parseInt((nextShowTimeSeconds/60)%60,10);//分钟:先计算成总共有多少分钟,再取分钟的余数 var nextSeconds = nextShowTimeSeconds%60;//秒:本来就是秒,直接获取最后剩下的秒就好了 var curHour = parseInt((curShowTimeSeconds/(60*60))%24,10);//小时 var curMinute = parseInt((curShowTimeSeconds/60)%60,10);//分钟:先计算成总共有多少分钟,再取分钟的余数 var curSeconds = curShowTimeSeconds%60;//秒:本来就是秒,直接获取最后剩下的秒就好了 //因为秒数变化的比较最快,所以判断秒数是否相同 if(nextSeconds!=curSeconds){ //判断哪个数字发生了变化,若变化了就添加当前时间下的小球 //小时 if(parseInt(curHour/10)!=parseInt(nextHour/10)){ addBall(MAGIN_LEFT+0,MAGIN_TOP,parseInt(curHour/10));//发生变化时,添加变化数字的弹跳小球 } if(parseInt(curHour%10)!=parseInt(nextHour%10)){ addBall(MAGIN_LEFT+(15*(RADIUS+1)),MAGIN_TOP,parseInt(curHour%10,10)); } //分钟 if(parseInt(curMinute/10)!=parseInt(nextMinute/10)){ addBall(MAGIN_LEFT+(39*(RADIUS+1)),MAGIN_TOP,parseInt(curMinute/10,10)); } if(parseInt(curMinute%10)!=parseInt(nextMinute%10)){ addBall(MAGIN_LEFT+(54*(RADIUS+1)),MAGIN_TOP,parseInt(curMinute%10,10)); } //秒 if(parseInt(curSeconds/10)!=parseInt(nextSeconds/10)){ addBall(MAGIN_LEFT+(78*(RADIUS+1)),MAGIN_TOP,parseInt(curSeconds/10,10)); } if(parseInt(curSeconds%10)!=parseInt(nextSeconds%10)){ addBall(MAGIN_LEFT+(93*(RADIUS+1)),MAGIN_TOP,parseInt(curSeconds%10,10)); } curShowTimeSeconds = nextShowTimeSeconds; } //更新正在画布上运动的小球 updateBalls(); //console.log(ball.length); } //该方法用来处理正在画布上运动的小球 function updateBalls(){ for(var i=0;i<ball.length;i++){ ball[i].x +=ball[i].vx; ball[i].y +=ball[i].vy; ball[i].vy +=ball[i].g; //判断小球是否碰壁,若是碰壁就减小y方向的高度,达到小球越跳越低的效果 if(ball[i].y>=WINDOW_HEIGHT-RADIUS){ ball[i].y=WINDOW_HEIGHT-RADIUS; ball[i].vy = -ball[i].vy*0.75;//慢慢降低弹跳小球的高度,达到小球越跳越低的效果 } } //判断小球是否还在画布中 var cnt = 0; for(var i=0;i<ball.length;i++){ if(ball[i].x+RADIUS>0 && ball[i].x-RADIUS<WINDOW_Width){//说明小球还在画布中 ball[cnt++]=ball[i];//将在画布中的小球全部添加到ball的前面 } } //通过前面for循环的处理,在画布中的小球全部集中到了ball数组的全面,那么后面部分即为不在屏幕中的小球,可以清空掉 //按道理while循环中的条件应该为:ball.length>cnt,这样子会有一个bug,就是:打开该页面以后,切换到别的页面,弹跳的数量会一直积累,达不到优化性能的效果 //所以修改为ball.length>Math.min(300,cnt),让画布中的弹跳小球保持在300附近 while(ball.length>Math.min(300,cnt)){ //pop() 方法用于删除并返回数组的最后一个元素。 ball.pop();//将数组末尾的小球删掉,避免内存中小球数量太多影响性能 } } /** *该方法用来添加弹跳的小球 *@param x:小球起点x坐标 *@param y:小球起点y坐标 *@param num:需要添加弹跳小球的数字 **/ function addBall(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)),//x坐标 y:(y+i*2*(RADIUS+1)+(RADIUS+1)),//y坐标 g:1.5+Math.random(),//加速度:设置为随机数,到小球弹跳不规则 vx:Math.pow(-1,Math.ceil(Math.random()*1000))*4,//小球在x方向的速度(-4或者+4) vy:-5,//小球在y方向的速度,让小球在生成时有一个向上抛的动作 color:colors[Math.floor(Math.random()*colors.length)]//小球的颜色 }; ball.push(aBall);//将生成的小球添加到ball数组中 } } } } /** *该方法用来解决“分秒为1位数以下时自动添加零变成2位数”的问题 *@param value:需要补零的值 */ function checkTime(value){ value = parseInt(value,10); if(value<10){ value = "0"+value; } return value; } /** *该方法用来绘制图形 *@param x:起点x坐标 *@param y:起点y坐标 *@param num:需要进行绘制的数字 *@param cxt:上下文绘图环境context **/ function renderDigit(x,y,num,cxt){ 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(); //RADIUS为圆球的半径,RADIUS+1作为盛放圆球的方形半径,1是多余出来的空间,那么方形的直径为:2*(RADIUS+1) //x+j*2*(RADIUS+1)+(RADIUS+1):x+j*2*(RADIUS+1)计算要绘制的球形所在前一个方形的位置, //(RADIUS+1)为下一个方形的一半,也就是要绘制的球形的中心 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();//绘制 } } } }
具体实例已经上传到附件,有需要的可以下载。记得在支持html5的浏览器中执行哦!