1.效果预览(文章最后面有源码)
2.代码实现
2.1 简要分析
这个案例可以大概分成三步来实现:首先绘制出时间,然后根据时间的变动产生相应的彩色小球,最后让这些小球运动起来。
第一步:通过观察我们可以看到,这些时间都是用一个个小球拼出来的,所以我们采用的方法就是利用一个点阵来表示一个数字,例如数字0就可以用如下的点阵来表示,具体绘制的时候,在数字1的位置进行圆球的绘制,数字0的位置就不绘制,最后就会达到右边的显示效果。
第二步:当时间变动后,对应的数字会发生变化,那么在变化后的数字那里会产生彩色的小球,我们这里可以按照第一步的方法来绘制小球,只不过这个小球是具有不同的颜色的。
第三步:让产生的彩色小球运动起来,只需要改变小球的坐标就可以了,但是这中间还会有一些参数的处理,使得小球按照我给出的预览图中的方式来进行运动。
2.2 初始化画布
2.2.1 创建canvas画布
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>倒计时</title>
<style>
*{
margin: 0;
padding: 0;
}
body,html{
height: 100%;
width: 100%;
}
</style>
</head>
<body>
<canvas id='mycanvas' style="display: block; margin: 30px auto; border: 2px solid pink">
当前浏览器不支持Canvas,请更换浏览器再试!
</canvas>
<!-- digit.js的内容是我们需要用到的数字和符号的点阵 -->
<script src="digit.js"></script>
<!-- countdown.js是我们来处理这个项目的逻辑的文件 -->
<script src="countdown2.js"></script>
</body>
</html>
2.2.2 设置画布的宽高
//设置一些全局变量,后面会多次用到
var WINDOW_WIDTH = 1024; //画布的宽度
var WINDOW_HEIGHT = 700; //画布的高度
var RADIUS = 8; //绘制的一个个小圆球的半径
var MARGIN_LEFT = 30; //绘制内容距离画布最左边的距离
var MARGIN_TOP = 60; //绘制内容距离画布最上面的距离
window.onload = function(){
//获取到canvas画布
var canvas = document.getElementById('mycanvas');
//获取到绘图的上下文环境
var context = canvas.getContext('2d');
//设置画布的宽高
canvas.width = WINDOW_WIDTH;
canvas.height = WINDOW_HEIGHT;
//进行绘制
render(context);
};
此时运行html文件之后会看到一个处于屏幕中心的带有粉色边框的矩形区域,这个就是我们的canvas画布。
2.3 绘制具体的数字
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();
cxt.arc(x+j*2*(RADIUS+1) + (RADIUS+1), y+i*2*(RADIUS+1) + (RADIUS+1), RADIUS, 0, 2*Math.PI, true);
cxt.closePath();
cxt.fill();
}
}
}
};
注意这里绘制小圆球时坐标的计算,我们这里小圆球的半径是RADIUS,但是为了美观,这里每个小正方形的边长是2*(RADIUS+1),这样相邻的小圆球之间就会有一些间隙。
2.4 绘制时间
既然知道了如何绘制一个具体的数字,那么绘制时间就很简单了,将时、分、秒对应的十位和个位的值计算出来,分别进行绘制就可以了,但是这里要注意,绘制的起始坐标的计算。每个数字都是一个十行七列的点阵,所以每个数字所占据的列数是14,同样的为了有一些间隙,我们将每个数字的列数设置为15。同样的由于冒号是十行四列,我们将它占据的列数设置为9。
//绘制的方法
function render(cxt){
//1.刷新绘图环境
cxt.clearRect(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT);
//2.获取到要绘制的时间
var hour = 12;
var minute = 34;
var second = 56;
//3.绘制具体数字
renderDigit(MARGIN_LEFT, MARGIN_TOP, parseInt(hour/10), cxt); //小时的十位
renderDigit(MARGIN_LEFT + 15*(RADIUS+1), MARGIN_TOP, parseInt(hour%10), cxt); //小时的个位
renderDigit(MARGIN_LEFT + 30*(RADIUS+1), MARGIN_TOP, 10, cxt); //冒号
renderDigit(MARGIN_LEFT + 39*(RADIUS+1), MARGIN_TOP, parseInt(minute/10), cxt); //分钟的十位
renderDigit(MARGIN_LEFT + 54*(RADIUS+1), MARGIN_TOP, parseInt(minute%10), cxt); //分钟的个位
renderDigit(MARGIN_LEFT + 69*(RADIUS+1), MARGIN_TOP, 10, cxt); //冒号
renderDigit(MARGIN_LEFT + 78*(RADIUS+1), MARGIN_TOP, parseInt(second/10), cxt); //秒钟的十位
renderDigit(MARGIN_LEFT + 93*(RADIUS+1), MARGIN_TOP, parseInt(second%10), cxt); //秒钟的个位
};
此时运行我们的程序,会得到绘制出来的时间 12:34:56
2.5 动态获取时间
上面我们设置的是一个固定的时间,那么接下来我们动态的来获取倒计时的时间。先设置一个结束时间,然后通过结束时间与当前时间的减法运算获取到倒计时时间,由于当前时间是在变化的,所以得到的倒计时时间也是变化的。
注意:我们这里由于小时只能显示两位数,所以最多只能设置99个小时的倒计时,所以结束时间不要设置的距离当前时间太久!
//获取时间的方法
const endTime = new Date(2019,7,28,21,0,0);
function getTime(){
//获取到当前时间
var curTime = new Date();
//计算得出倒计时的时间(这里的单位是毫秒)
var res = endTime.getTime() - curTime.getTime();
//返回转换后的值
res = parseInt(res/1000);
return res >= 0 ? res : 0;
};
//更新时间的方法
function update(){
//再次获取当前时间,通过与之前获取的时间进行对比,就可以知道时间是否发生了变化
var nextTimeSeconds = getTime();
var nextHour = parseInt(nextTimeSeconds/3600);
var nextMinute = parseInt((nextTimeSeconds-nextHour*3600) / 60);
var nextSecond = nextTimeSeconds % 60;
var curHour = parseInt(curTimeSeconds/3600);
var curMinute = parseInt((curTimeSeconds-curHour*3600) / 60);
var curSecond = curTimeSeconds % 60;
if( curSecond != nextSecond ){
//时间发生了变化,故修改之前获取的时间
curTimeSeconds = nextTimeSeconds;
};
};
动态地获取到时间后,我们要对两个地方进行修改。首先在window.onload函数里面开启一个定时器,这样就可以不断地重新绘制,就可以将动态获取的时间实时地绘制出来;然后就是我们在render方法里面,之前都是写的固定的时间,我们这里要将动态获取到的时间写进去。
var curTimeSeconds = 0 //提到前面,编程一个全局变量,因为后面有多个函数会用到这个变量
window.onload = function(){
//获取到canvas画布
var canvas = document.getElementById('mycanvas');
//获取到绘图的上下文环境
var context = canvas.getContext('2d');
//设置画布的宽高
canvas.width = WINDOW_WIDTH;
canvas.height = WINDOW_HEIGHT;
//获取到时间
curTimeSeconds = getTime();
//开启定时器
setInterval(function(){
//进行绘制
render(context);
//更新时间
update();
},50);
};
function render(cxt){
//1.刷新绘图环境
cxt.clearRect(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT);
//2.获取到要绘制的时间
var hour = parseInt(curTimeSeconds/3600);
var minute = parseInt((curTimeSeconds-hour*3600) / 60);
var second = curTimeSeconds % 60;
//3.绘制具体数字
renderDigit(MARGIN_LEFT, MARGIN_TOP, parseInt(hour/10), cxt);
renderDigit(MARGIN_LEFT + 15*(RADIUS+1), MARGIN_TOP, parseInt(hour%10), cxt);
renderDigit(MARGIN_LEFT + 30*(RADIUS+1), MARGIN_TOP, 10, cxt);
renderDigit(MARGIN_LEFT + 39*(RADIUS+1), MARGIN_TOP, parseInt(minute/10), cxt);
renderDigit(MARGIN_LEFT + 54*(RADIUS+1), MARGIN_TOP, parseInt(minute%10), cxt);
renderDigit(MARGIN_LEFT + 69*(RADIUS+1), MARGIN_TOP, 10, cxt);
renderDigit(MARGIN_LEFT + 78*(RADIUS+1), MARGIN_TOP, parseInt(second/10), cxt);
renderDigit(MARGIN_LEFT + 93*(RADIUS+1), MARGIN_TOP, parseInt(second%10), cxt);
};
现在再运行项目,我们就可以看到以及实现了倒计时的效果,距离我们的目标,还剩下添加彩色的小球,以及使其运动起来。
2.6 添加小球
只有时间发生了变化的时候,我们才会添加小球,所以我们要在更新时间的update方法里面进行小球的添加。添加小球其实与之前绘制时间时的绘制小球的方法一致,所以我们要在render方法里面绘制添加的小球。那么我们如何在render方法里面获取到在update方法中添加的小球呢?
方法就是我们设置一个全局变量balls,它是一个空数组,在update方法中添加小球时,我们用一个新的方法来专门实现小球的添加,这个方法里面,我们设置好每个小球的一些属性,让后将小球作为一个对象,添加到数组balls中,然后我们就可以在render方法中通过全局变量balls获取到添加的小球,从而进行绘制。
//更新时间的方法
function update(){
//再次获取当前时间,通过与之前获取的时间进行对比,就可以知道时间是否发生了变化
var nextTimeSeconds = getTime();
var nextHour = parseInt(nextTimeSeconds/3600);
var nextMinute = parseInt((nextTimeSeconds-nextHour*3600) / 60);
var nextSecond = nextTimeSeconds % 60;
var curHour = parseInt(curTimeSeconds/3600);
var curMinute = parseInt((curTimeSeconds-curHour*3600) / 60);
var curSecond = curTimeSeconds % 60;
if( curSecond != nextSecond ){
//添加小球
//小时的十位发生了变化
if( parseInt(curHour/10) != parseInt(nextHour/10) ){
addBalls( MARGIN_LEFT + 0 , MARGIN_TOP , parseInt(curHour/10) );
}
//小时的个位发生了变化
if( parseInt(curHour%10) != parseInt(nextHour%10) ){
addBalls( MARGIN_LEFT + 15*(RADIUS+1) , MARGIN_TOP , parseInt(curHour/10) );
}
if( parseInt(curMinute/10) != parseInt(nextMinute/10) ){
addBalls( MARGIN_LEFT + 39*(RADIUS+1) , MARGIN_TOP , parseInt(curMinute/10) );
}
if( parseInt(curMinute%10) != parseInt(nextMinute%10) ){
addBalls( MARGIN_LEFT + 54*(RADIUS+1) , MARGIN_TOP , parseInt(curMinute%10) );
}
if( parseInt(curSecond/10) != parseInt(nextSecond/10) ){
addBalls( MARGIN_LEFT + 78*(RADIUS+1) , MARGIN_TOP , parseInt(curSecond/10) );
}
if( parseInt(curSecond%10) != parseInt(nextSecond%10) ){
addBalls( MARGIN_LEFT + 93*(RADIUS+1) , MARGIN_TOP , parseInt(nextSecond%10) );
}
//时间发生了变化,故修改之前获取的时间
curTimeSeconds = nextTimeSeconds;
};
};
var balls = []; //用来存储小球
//存储一些颜色的值
const colors = ["#33B5E5","#0099CC","#AA66CC","#9933CC","#99CC00","#669900","#FFBB33","#FF8800","#FF4444","#CC0000"];
//添加小球的方法
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), //小球的纵坐标
color: colors[ Math.floor( Math.random()*colors.length ) ], //小球的颜色
//下面三个后面让小球运动起来会用到的一些属性
g:1.5+Math.random(), //加速度
//水平速度,这里的表达式的意思就是取负一的0到1000次方,然后再乘以4,最后的值就是4或者-4
vx:Math.pow( -1 , Math.ceil( Math.random()*1000 ) ) * 4,
vy:-5, //初始的垂直速度
}
balls.push( aBall ) //将创建的小球对象放入数组中
}
}
}
};
function render(cxt){
//1.刷新绘图环境
cxt.clearRect(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT);
//2.获取到要绘制的时间
var hour = parseInt(curTimeSeconds/3600);
var minute = parseInt((curTimeSeconds-hour*3600) / 60);
var second = curTimeSeconds % 60;
//3.绘制具体数字
renderDigit(MARGIN_LEFT, MARGIN_TOP, parseInt(hour/10), cxt); //小时的十位
renderDigit(MARGIN_LEFT + 15*(RADIUS+1), MARGIN_TOP, parseInt(hour%10), cxt); //小时的个位
renderDigit(MARGIN_LEFT + 30*(RADIUS+1), MARGIN_TOP, 10, cxt); //冒号
renderDigit(MARGIN_LEFT + 39*(RADIUS+1), MARGIN_TOP, parseInt(minute/10), cxt); //分钟的十位
renderDigit(MARGIN_LEFT + 54*(RADIUS+1), MARGIN_TOP, parseInt(minute%10), cxt); //分钟的个位
renderDigit(MARGIN_LEFT + 69*(RADIUS+1), MARGIN_TOP, 10, cxt); //冒号
renderDigit(MARGIN_LEFT + 78*(RADIUS+1), MARGIN_TOP, parseInt(second/10), cxt); //秒钟的十位
renderDigit(MARGIN_LEFT + 93*(RADIUS+1), MARGIN_TOP, parseInt(second%10), cxt); //秒钟的个位
//4.绘制添加的小球
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 , 2*Math.PI, true);
cxt.closePath();
cxt.fill();
}
};
现在,我们就已经成功添加好了小球,但是现在是有问题的,因为我们只让小球显示出来了,但是没有让它运动起来,这样彩色小球就会覆盖住数字,那么接下来我们就让小球运动起来。
2.7 让小球运动起来
想让小球运动起来,我们只需要改变小球的坐标即可,但是这里如何去修改坐标,是有一定技巧的,我们在创建小球的时候有一些跟运动相关的属性,其实原理和初中物理学到的抛物线运动是一样的,具有一个水平速度,确保小球水平方向上的运动,我们设置的水平速度只有两个值4和-4,所以小球会向左或者向右运动,我们这里设置的初始垂直速度是一个负值,所以小球会先向上运动然后再向下。注意:为了达到我们预想的效果,我们这里需要设置一个碰撞检测,当小球碰到画布底部的时候,让他弹起,继续运动,直至在水平方向上运动到画布之外,这样就更真实一些。改变小球的运动状态也是一个更新的操作,所以我们放在update方法中进行。
//更新时间和小球运动状态的方法
function update(){
//1.再次获取当前时间,通过与之前获取的时间进行对比,就可以知道时间是否发生了变化
var nextTimeSeconds = getTime();
var nextHour = parseInt(nextTimeSeconds/3600);
var nextMinute = parseInt((nextTimeSeconds-nextHour*3600) / 60);
var nextSecond = nextTimeSeconds % 60;
var curHour = parseInt(curTimeSeconds/3600);
var curMinute = parseInt((curTimeSeconds-curHour*3600) / 60);
var curSecond = curTimeSeconds % 60;
if( curSecond != nextSecond ){
//添加小球
if( parseInt(curHour/10) != parseInt(nextHour/10) ){
addBalls( MARGIN_LEFT + 0 , MARGIN_TOP , parseInt(curHour/10) );
}
if( parseInt(curHour%10) != parseInt(nextHour%10) ){
addBalls( MARGIN_LEFT + 15*(RADIUS+1) , MARGIN_TOP , parseInt(curHour/10) );
}
if( parseInt(curMinute/10) != parseInt(nextMinute/10) ){
addBalls( MARGIN_LEFT + 39*(RADIUS+1) , MARGIN_TOP , parseInt(curMinute/10) );
}
if( parseInt(curMinute%10) != parseInt(nextMinute%10) ){
addBalls( MARGIN_LEFT + 54*(RADIUS+1) , MARGIN_TOP , parseInt(curMinute%10) );
}
if( parseInt(curSecond/10) != parseInt(nextSecond/10) ){
addBalls( MARGIN_LEFT + 78*(RADIUS+1) , MARGIN_TOP , parseInt(curSecond/10) );
}
if( parseInt(curSecond%10) != parseInt(nextSecond%10) ){
addBalls( MARGIN_LEFT + 93*(RADIUS+1) , MARGIN_TOP , parseInt(nextSecond%10) );
}
//时间发生了变化,故修改之前获取的时间
curTimeSeconds = nextTimeSeconds;
};
//2.改变小球运动状态
updateBalls();
};
//更新小球运动状态的方法
function updateBalls(){
for(var i=0; i<balls.length; i++){
//1.改变小球的坐标
balls[i].x += balls[i].vx;
balls[i].y += balls[i].vy;
balls[i].vy += balls[i].g;
//2.碰撞检测
if(balls[i].y >= WINDOW_HEIGHT - RADIUS){
balls[i].y = WINDOW_HEIGHT - RADIUS;
balls[i].vy = -balls[i].vy * 0.6;
}
};
};
好的,到目前位为止,我们就已经实现了小球的动画效果了,但是,还有一个问题,那就是我们之前添加小球时根据时间的变化来添加的,时间是一直在变化的,所以小球就会一直不断地产生,但是我们却从来都没有删除过小球,所以小球就会越来越多,这是很占电脑内存的,越往后你就会发现电脑就会变卡。所以我们要将超出画布范围的小球清除掉。
function updateBalls(){
var cnt = 0 ; //定义一个变量,用于小球的清除
for(var i=0; i<balls.length; i++){
//1.改变小球的坐标
balls[i].x += balls[i].vx;
balls[i].y += balls[i].vy;
balls[i].vy += balls[i].g;
//2.碰撞检测
if(balls[i].y >= WINDOW_HEIGHT - RADIUS){
balls[i].y = WINDOW_HEIGHT - RADIUS;
balls[i].vy = -balls[i].vy * 0.6;
}
//3.清除超出画布范围的小球
if(balls[i].x + RADIUS > 0 && balls[i].x - RADIUS < WINDOW_WIDTH){
//将还在画布内的小球放到balls数组的最前面
balls[cnt++] = balls[i];
}
};
//在画布内的小球都放到了数组的前cnt个,所以后面的所有小球都是画布外的
while(balls.length > cnt){
balls.pop(); //删除数组末尾的一个元素
}
};
注意:这里cnt是从零开始的,所以balls[cnt++] = balls[i]这段代码,就将还在画布内的小球balls[i],存储为balls[cnt],cnt从零开始递增,那么符合要求的小球就都被排在了数组balls的最前面,所以只要是索引大于cnt的小球,都是已经超出画布范围的。
2.8 屏幕自适应
之前的画布的宽高以及小球半径的大小都是我们写的固定的值,现在我们通过公式换算,实现屏幕自适应。这里要注意将canvas的高设置为百分百,并且将前面设置的display、margin、border属性都删除,以及这里圆球半径的计算。
//设置一些全局变量,后面会多次用到
var WINDOW_WIDTH; //画布的宽度
var WINDOW_HEIGHT; //画布的高度
var RADIUS; //绘制的一个个小圆球的半径
var MARGIN_LEFT; //绘制内容距离画布最左边的距离
var MARGIN_TOP; //绘制内容距离画布最上面的距离
var curTimeSeconds = 0 //提到前面,编程一个全局变量,因为后面有多个函数会用到这个变量
window.onload = function(){
//获取到屏幕的宽高
WINDOW_WIDTH = document.body.clientWidth;
WINDOW_HEIGHT = document.body.clientHeight;
//将两边的间距设置为屏幕宽度的十分之一,两边加起来就是五分之一了
MARGIN_LEFT = parseInt(WINDOW_WIDTH/10);
//用来绘制的宽度占总宽度的五分之四,前面我们计算出来的所有数字和冒号总共占据的列数位108
//但是是108*(RADIUS+1), 所以不要忘记最后要减去一。
RADIUS = parseInt(WINDOW_WIDTH*4/5/108) - 1;
//将上方的间距设置为总高度的五分之一
MARGIN_TOP = parseInt(WINDOW_HEIGHT/5);
//获取到canvas画布
var canvas = document.getElementById('mycanvas');
//获取到绘图的上下文环境
var context = canvas.getContext('2d');
//设置画布的宽高
canvas.width = WINDOW_WIDTH;
canvas.height = WINDOW_HEIGHT;
//获取到时间
curTimeSeconds = getTime();
//开启定时器
setInterval(function(){
//进行绘制
render(context);
//更新时间
update();
},50);
};
好的,到目前为止,就算是大功告成了,我们炫酷的倒计时效果就出来了。
3.总结
3.1 注意点
3.1.1 小球的横纵坐标的计算
3.1.2 数字和冒号占据的列数的计算
3.1.3 小球的运动状态的变化,相关属性以及参数的设置
3.1.4 小球的清除,变量cnt的作用
3.2 扩展
我们这里设置的结束时间是我们自己设定的,这样要是超过了这个时间,这个倒计时效果就不存在了,这样好像实用性不强,那么我们可不可以改造一下,使得每次运行,倒计时都是从一个小时开始呢?
其实我们只要修改一些结束时间就可以了,将结束时间设置为当前时间的后面一个小时。
// const endTime = new Date(2019,7,28,21,0,0);
//设置时间为当前时间的后面一个小时
var endTime = new Date();
endTime.setTime(endTime.getTime() + 3600 * 1000); //这里要将一个小时代表的3600秒转化为毫秒,故乘以一千
//获取时间的方法
function getTime(){
//获取到当前时间
var curTime = new Date();
//计算得出倒计时的时间(这里的单位是毫秒)
var res = endTime.getTime() - curTime.getTime();
//返回转换后的值
res = parseInt(res/1000);
return res >= 0 ? res : 0;
};
这样每次运行,都是从一个小时开始倒计时,这样的话,就变得很实用了。其实我们还可以将其改造为一个时钟,显示当前时间,不过相比于一个小时倒计时,实用性还是差一些。如果改造成时钟的话,就不需要设置结束时间了。
// const endTime = new Date(2019,7,28,21,0,0);
/*
//设置时间为当前时间的后面一个小时
var endTime = new Date();
endTime.setTime(endTime.getTime() + 3600 * 1000); //这里要将一个小时代表的3600秒转化为毫秒,故乘以一千
*/
//获取时间的方法
function getTime(){
//获取到当前时间,这里获取到的时间是距离1970年1月1日凌晨零点的毫秒数
var curTime = new Date();
//计算出当前时间一共有多少秒
var res = curTime.getHours()*3600 + curTime.getMinutes()*60 + curTime.getSeconds();
//返回得到的数值
return res;
};
4. 源码(一小时倒计时的源码)
4.1 html文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>倒计时</title>
<style>
*{
margin: 0;
padding: 0;
}
body,html{
height: 100%;
width: 100%;
}
</style>
</head>
<body>
<canvas id='mycanvas' style="height:100%">
当前浏览器不支持Canvas,请更换浏览器再试!
</canvas>
<!-- digit.js的内容是我们需要用到的数字和符号的点阵 -->
<script src="digit.js"></script>
<!-- countdown.js是我们来处理这个项目的逻辑的文件 -->
<script src="countdown2.js"></script>
</body>
</html>
4.2 digit.js文件(点阵)
digit=[
[
[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,0,0,1,1,0,0],
[0,1,1,1,1,0,0],
[0,0,0,1,1,0,0],
[0,0,0,1,1,0,0],
[0,0,0,1,1,0,0],
[0,0,0,1,1,0,0],
[0,0,0,1,1,0,0],
[0,0,0,1,1,0,0],
[0,0,0,1,1,0,0],
[1,1,1,1,1,1,1]
], //1
[
[0,1,1,1,1,1,0],
[1,1,0,0,0,1,1],
[0,0,0,0,0,1,1],
[0,0,0,0,1,1,0],
[0,0,0,1,1,0,0],
[0,0,1,1,0,0,0],
[0,1,1,0,0,0,0],
[1,1,0,0,0,0,0],
[1,1,0,0,0,1,1],
[1,1,1,1,1,1,1]
], //2
[
[1,1,1,1,1,1,1],
[0,0,0,0,0,1,1],
[0,0,0,0,1,1,0],
[0,0,0,1,1,0,0],
[0,0,1,1,1,0,0],
[0,0,0,0,1,1,0],
[0,0,0,0,0,1,1],
[0,0,0,0,0,1,1],
[1,1,0,0,0,1,1],
[0,1,1,1,1,1,0]
], //3
[
[0,0,0,0,1,1,0],
[0,0,0,1,1,1,0],
[0,0,1,1,1,1,0],
[0,1,1,0,1,1,0],
[1,1,0,0,1,1,0],
[1,1,1,1,1,1,1],
[0,0,0,0,1,1,0],
[0,0,0,0,1,1,0],
[0,0,0,0,1,1,0],
[0,0,0,1,1,1,1]
], //4
[
[1,1,1,1,1,1,1],
[1,1,0,0,0,0,0],
[1,1,0,0,0,0,0],
[1,1,1,1,1,1,0],
[0,0,0,0,0,1,1],
[0,0,0,0,0,1,1],
[0,0,0,0,0,1,1],
[0,0,0,0,0,1,1],
[1,1,0,0,0,1,1],
[0,1,1,1,1,1,0]
], //5
[
[0,0,0,0,1,1,0],
[0,0,1,1,0,0,0],
[0,1,1,0,0,0,0],
[1,1,0,0,0,0,0],
[1,1,0,1,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],
[0,1,1,1,1,1,0]
], //6
[
[1,1,1,1,1,1,1],
[1,1,0,0,0,1,1],
[0,0,0,0,1,1,0],
[0,0,0,0,1,1,0],
[0,0,0,1,1,0,0],
[0,0,0,1,1,0,0],
[0,0,1,1,0,0,0],
[0,0,1,1,0,0,0],
[0,0,1,1,0,0,0],
[0,0,1,1,0,0,0]
], //7
[
[0,1,1,1,1,1,0],
[1,1,0,0,0,1,1],
[1,1,0,0,0,1,1],
[1,1,0,0,0,1,1],
[0,1,1,1,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],
[0,1,1,1,1,1,0]
], //8
[
[0,1,1,1,1,1,0],
[1,1,0,0,0,1,1],
[1,1,0,0,0,1,1],
[1,1,0,0,0,1,1],
[0,1,1,1,0,1,1],
[0,0,0,0,0,1,1],
[0,0,0,0,0,1,1],
[0,0,0,0,1,1,0],
[0,0,0,1,1,0,0],
[0,1,1,0,0,0,0]
], //9
[
[0,0,0,0],
[0,0,0,0],
[0,1,1,0],
[0,1,1,0],
[0,0,0,0],
[0,0,0,0],
[0,1,1,0],
[0,1,1,0],
[0,0,0,0],
[0,0,0,0]
] //冒号:
]
4.3 countdown.js(逻辑处理)
//设置一些全局变量,后面会多次用到
var WINDOW_WIDTH; //画布的宽度
var WINDOW_HEIGHT; //画布的高度
var RADIUS; //绘制的一个个小圆球的半径
var MARGIN_LEFT; //绘制内容距离画布最左边的距离
var MARGIN_TOP; //绘制内容距离画布最上面的距离
var curTimeSeconds = 0 //提到前面,变成一个全局变量,因为后面有多个函数会用到这个变量
//const endTime = new Date(2019,7,28,21,0,0); 这里月份是从零开始的,所以7代表八月
//设置结束时间为当前时间的后面一个小时
var endTime = new Date();
endTime.setTime(endTime.getTime() + 3600 * 1000); //这里要将一个小时代表的3600秒转化为毫秒,故乘以一千
var balls = []; //用来存储小球
//存储一些颜色的值
const colors = ["#33B5E5","#0099CC","#AA66CC","#9933CC","#99CC00","#669900","#FFBB33","#FF8800","#FF4444","#CC0000"];
window.onload = function(){
//获取到屏幕的宽高
WINDOW_WIDTH = document.body.clientWidth;
WINDOW_HEIGHT = document.body.clientHeight;
//将两边的间距设置为屏幕宽度的十分之一,两边加起来就是五分之一了
MARGIN_LEFT = parseInt(WINDOW_WIDTH/10);
//用来绘制的宽度占总宽度的五分之四,前面我们计算出来的所有数字和冒号总共占据的列数位108
//但是是108*(RADIUS+1), 所以不要忘记最后要减去一。
RADIUS = parseInt(WINDOW_WIDTH*4/5/108) - 1;
//将上方的间距设置为总高度的五分之一
MARGIN_TOP = parseInt(WINDOW_HEIGHT/5);
//获取到canvas画布
var canvas = document.getElementById('mycanvas');
//获取到绘图的上下文环境
var context = canvas.getContext('2d');
//设置画布的宽高
canvas.width = WINDOW_WIDTH;
canvas.height = WINDOW_HEIGHT;
//获取到时间
curTimeSeconds = getTime();
//开启定时器
setInterval(function(){
//进行绘制
render(context);
//更新时间、添加小球、改变小球运动状态
update();
},50);
};
//绘制的方法
function render(cxt){
//1.刷新绘图环境
cxt.clearRect(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT);
//2.获取到要绘制的时间
var hour = parseInt(curTimeSeconds/3600);
var minute = parseInt((curTimeSeconds-hour*3600) / 60);
var second = curTimeSeconds % 60;
//3.绘制具体数字
renderDigit(MARGIN_LEFT, MARGIN_TOP, parseInt(hour/10), cxt); //小时的十位
renderDigit(MARGIN_LEFT + 15*(RADIUS+1), MARGIN_TOP, parseInt(hour%10), cxt); //小时的个位
renderDigit(MARGIN_LEFT + 30*(RADIUS+1), MARGIN_TOP, 10, cxt); //冒号
renderDigit(MARGIN_LEFT + 39*(RADIUS+1), MARGIN_TOP, parseInt(minute/10), cxt); //分钟的十位
renderDigit(MARGIN_LEFT + 54*(RADIUS+1), MARGIN_TOP, parseInt(minute%10), cxt); //分钟的个位
renderDigit(MARGIN_LEFT + 69*(RADIUS+1), MARGIN_TOP, 10, cxt); //冒号
renderDigit(MARGIN_LEFT + 78*(RADIUS+1), MARGIN_TOP, parseInt(second/10), cxt); //秒钟的十位
renderDigit(MARGIN_LEFT + 93*(RADIUS+1), MARGIN_TOP, parseInt(second%10), cxt); //秒钟的个位
//4.绘制添加的小球
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 , 2*Math.PI, true);
cxt.closePath();
cxt.fill();
}
};
//绘制具体数字的方法
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();
cxt.arc(x+j*2*(RADIUS+1) + (RADIUS+1), y+i*2*(RADIUS+1) + (RADIUS+1), RADIUS, 0, 2*Math.PI, true);
cxt.closePath();
cxt.fill();
}
}
}
};
//获取时间的方法
function getTime(){
//获取到当前时间,这里获取到的时间是距离1970年1月1日凌晨零点的毫秒数
var curTime = new Date();
//计算得出倒计时的时间(这里的单位是毫秒)
var res = endTime.getTime() - curTime.getTime();
//返回转换后的值
res = parseInt(res/1000);
return res >= 0 ? res : 0;
};
//更新时间和小球运动状态的方法
function update(){
//1.再次获取当前时间,通过与之前获取的时间进行对比,就可以知道时间是否发生了变化
var nextTimeSeconds = getTime();
var nextHour = parseInt(nextTimeSeconds/3600);
var nextMinute = parseInt((nextTimeSeconds-nextHour*3600) / 60);
var nextSecond = nextTimeSeconds % 60;
var curHour = parseInt(curTimeSeconds/3600);
var curMinute = parseInt((curTimeSeconds-curHour*3600) / 60);
var curSecond = curTimeSeconds % 60;
if( curSecond != nextSecond ){
//添加小球
if( parseInt(curHour/10) != parseInt(nextHour/10) ){
addBalls( MARGIN_LEFT + 0 , MARGIN_TOP , parseInt(curHour/10) );
}
if( parseInt(curHour%10) != parseInt(nextHour%10) ){
addBalls( MARGIN_LEFT + 15*(RADIUS+1) , MARGIN_TOP , parseInt(curHour/10) );
}
if( parseInt(curMinute/10) != parseInt(nextMinute/10) ){
addBalls( MARGIN_LEFT + 39*(RADIUS+1) , MARGIN_TOP , parseInt(curMinute/10) );
}
if( parseInt(curMinute%10) != parseInt(nextMinute%10) ){
addBalls( MARGIN_LEFT + 54*(RADIUS+1) , MARGIN_TOP , parseInt(curMinute%10) );
}
if( parseInt(curSecond/10) != parseInt(nextSecond/10) ){
addBalls( MARGIN_LEFT + 78*(RADIUS+1) , MARGIN_TOP , parseInt(curSecond/10) );
}
if( parseInt(curSecond%10) != parseInt(nextSecond%10) ){
addBalls( MARGIN_LEFT + 93*(RADIUS+1) , MARGIN_TOP , parseInt(nextSecond%10) );
}
//时间发生了变化,故修改之前获取的时间
curTimeSeconds = nextTimeSeconds;
};
//2.改变小球运动状态
updateBalls();
};
//添加小球的方法
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), //小球的纵坐标
color: colors[ Math.floor( Math.random()*colors.length ) ], //小球的颜色
//下面三个后面让小球运动起来会用到的一些属性
g:1.5+Math.random(), //加速度
//水平速度,这里的表达式的意思就是取负一的0到1000次方,然后再乘以4,最后的值就是4或者-4
vx:Math.pow( -1 , Math.ceil( Math.random()*1000 ) ) * 4,
vy:-5, //初始的垂直速度
}
balls.push( aBall ) //将创建的小球对象放入数组中
}
}
}
};
//更新小球运动状态的方法
function updateBalls(){
var cnt = 0 ; //定义一个变量,用于小球的清除
for(var i=0; i<balls.length; i++){
//1.改变小球的坐标
balls[i].x += balls[i].vx;
balls[i].y += balls[i].vy;
balls[i].vy += balls[i].g;
//2.碰撞检测
if(balls[i].y >= WINDOW_HEIGHT - RADIUS){
balls[i].y = WINDOW_HEIGHT - RADIUS;
balls[i].vy = -balls[i].vy * 0.6;
}
//3.清除超出画布范围的小球
if(balls[i].x + RADIUS > 0 && balls[i].x - RADIUS < WINDOW_WIDTH){
//将还在画布内的小球放到balls数组的最前面
balls[cnt++] = balls[i];
}
};
//在画布内的小球都放到了数组的前cnt个,所以后面的所有小球都是画布外的
while(balls.length > cnt){
balls.pop(); //删除数组末尾的一个元素
}
};