利用canvas实现炫酷的倒计时效果

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(); //删除数组末尾的一个元素
    }

};

        

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值