绚丽的时钟效果学习总结

一、背景

本文是在学习慕课网的“绚丽的倒计时效果-canvas绘图与动画基础”后总结而成。
每次变换就有对应的球掉落,成品效果图如下:
时钟

二、学习收获

  1. 以像素来构图的思路
    本例中以二维矩阵来显示每一个数字,使用一个7*10的点阵,1表明画圆,0不画,最后组成每个数字,组成的方式也值得学习,也还可以换其他风格的数字,字母等。

  2. 屏幕自适应处理
    需做的工作:
    a).在html、body、canvas中增加样式height:100%,
    b).使用document.body的clientHeight和clientWidth方法取到body的宽高。
    c).规划好左、右、上的空白比例,以此计算每个数字的起始显示位置和小球的半径。
    由上可知,自适应的一种策略可以是:先以宏定义宽高,在某一个屏幕大小调好其他的效果后,最后根据需要的布局从外到内的计算具体的尺寸。

  3. 动画基础
    setInterval(func,interval)方法定义定时器,每间隔interval调用func函数。
    func函数中做两件事:一是重绘画布;二是更新数据,指定在重绘下一帧时使用的数据。

  4. 物理过程模拟
    ball = {x:40,y:50,vx:4,vy:2,g:3}定义小球的起始位置x、y,水平和垂直vx、vy,垂直加速度g
    每次更新数据时x = x + vx; y = y + vy; vy = vy + g;
    碰撞检测:当x > height - radio 时,vy = - vy * a; //height为画布宽度,radio为球半径,a为系数,保证反弹后最大高度降低。

  5. 性能优化
    在检测到水平方向球在屏幕外时,从balls数组中移除元素的方法,很是值得借鉴:
    var avail = 0;
    for(var i = 0; i < balls.length; i++){
    if(balls[i].x > -circleRadius && balls[i].x < WINDOW_WIDTH + circleRadius){
    balls[avail++] = balls[i];
    }
    }
    从前向后检测,合法就移到数组前面,最后avail就是检测合格,需要保留的元素,之后可以使用:
    while(balls.length > avail){
    balls.pop();
    }
    调整数组,也可以简单的使用balls.length = avail 达到相同的缩减数组的效果。
    原教程中对最终数组长度有这样的优化:取Math.max(400,avail); 400为某一实践值,但我觉得这样不好,会使动画不连续。
    针对此处,有自己做过的一些改进:
    原代码parseInt(Math.random()4) Math.pow(-1,parseInt(Math.random()*1000))的问题在于,使用的vx为从0开始的随机数,范围±4,这里会引入一个问题,请看如下分析:
    假设球的水平速度均为v,画布宽度为w,因为每秒都会变换秒针,即使只考虑每秒只变一位数,每次只增加10+7=17个球,场内球数量达到平衡时总数有17*w/v个球
    因为是随机,当v随到0或接近0时,场内球数仍为无穷大,所以即使将越界的球删除了,但球的个数还是会无限的增加下去,针对此情况,可将水平方向的数组这样设置:parseInt(3 + Math.random()4) Math.pow(-1,parseInt(Math.random()*1000)),此时速度范围为(-7,-3] 与[3,7),正负只是决定了从左还是右出屏幕,速度的绝对值才是影响存在屏幕上的球数量的关键点,此时速度最小为3,即场上球数量最多为17*w/3,当然需要指出的是,这个数值不严谨,考虑到极端情况6位数字都变化,也只是在此数值上乘以6,数量级上相差不大,而且极端情况也不会一直发生。

  6. canvas的api
    此部分最重要的思想就是画图是分构思和执行两步的,strokeStyle,fillStyle,moveTo,lineTo,lineWidth,arc等都是在调整画笔的状态(位置,颜色,宽度,弧线),只有在stroke和fill方法使用后才是真正画到画布上。
    beginPath、closePath、clearRact等方法则是清楚画笔之前的状态,避免画笔前后状态冲突的方法。

三、完整代码

附上最后可工作的代码:

主体的Timer.html文件:

<!DOCTYPE html>
<html style="height:98%;margin:0">
<head lang="en">
    <meta charset="UTF-8">
    <title>Timer</title>
</head>
<body style="height:100%">
    <canvas id="canvas" style="height:100%;margin:0">
    浏览器不支持canvas
    </canvas>
</body >
    <script type="text/javascript" src="digit.js"></script>
    <script type="text/javascript" src="timer.js"></script>
</html>

用于表示0-9以及冒号的点阵数据digit.js:

var digit = [
    [                               //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]
    ],[                             //1
        [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]
    ],[                             //2
        [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]
    ],[                             //3
        [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, 0, 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, 0, 0]
    ],[                             //4
        [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]
    ],[                             //5
        [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]
    ],[                             //6
        [0, 0, 0, 0, 0, 1, 1],
        [0, 0, 0, 1, 1, 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]
    ],[                             //7
        [1, 1, 1, 1, 1, 1, 1],
        [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, 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]
    ],[                             //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, 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],
    ],[                             //9
        [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]
    ],[                             //:
        [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]
    ]
];

最后是主要的画图逻辑timer.js

var WINDOW_WIDTH = 1024;
var WINDOW_HEIGHT = 768;
var BASE_LEFT = 50;
var BASE_TOP = 50;
var circleRadius = 7;

const endTime = new Date(2016,0,23,23,05,00);
var currShowTimeSeconds = 0;

var balls =[];
var color = ['#C34EAB','#6C4E8A','#D6F86D','#7CF82F','#F73485','#6ACAE5','#F4CAD9','#4D085E','#ED977F','#CEE5DD'];
window.onload = function () {
    WINDOW_HEIGHT = document.body.clientHeight;
    WINDOW_WIDTH = document.body.clientWidth;

    BASE_LEFT = Math.round(WINDOW_WIDTH / 10);
    circleRadius = Math.round(WINDOW_WIDTH * 4 / 5 / 108) - 1;
    BASE_TOP = Math.round(WINDOW_HEIGHT / 5);

    var canvas = document.getElementById('canvas');
    canvas.width = WINDOW_WIDTH;
    canvas.height = WINDOW_HEIGHT;
    var context = canvas.getContext('2d');
    currShowTimeSeconds = getCurrShowTimeSeconds();
    setInterval(function(){
        drawTime(context);
        update();
    },50);
    drawTime(context);
}

function update(){
    var nextShowTimeSeconds = getCurrShowTimeSeconds();
    var nextHour = parseInt(nextShowTimeSeconds / 3600);
    var nextMinuter = parseInt((nextShowTimeSeconds - nextHour * 3600) / 60);
    var nextSecond = parseInt(nextShowTimeSeconds % 60);

    var hour = parseInt(currShowTimeSeconds / 3600);
    var minuter = parseInt((currShowTimeSeconds - hour * 3600)/60);
    var second = parseInt(currShowTimeSeconds % 60);
    if(second != nextSecond){
        currShowTimeSeconds = nextShowTimeSeconds;
        if(parseInt(nextHour / 10) != parseInt(hour / 10)){
            addBalls(BASE_LEFT,BASE_TOP,parseInt(nextHour / 10));
        }
        if(parseInt(nextHour % 10) != parseInt(hour % 10)){
            addBalls( BASE_LEFT + 15 * (circleRadius + 1),BASE_TOP,parseInt(nextHour % 10));
        }
        if(parseInt(nextMinuter / 10) != parseInt(minuter / 10)){
            addBalls(BASE_LEFT + 39 * (circleRadius + 1),BASE_TOP,parseInt(nextMinuter / 10));
        }
        if(parseInt(nextMinuter % 10) != parseInt(minuter % 10)){
            addBalls(BASE_LEFT + 54 * (circleRadius + 1),BASE_TOP,parseInt(nextMinuter % 10));
        }
        if(parseInt(nextSecond / 10) != parseInt(second / 10)){
            addBalls(BASE_LEFT + 78 * (circleRadius + 1),BASE_TOP,parseInt(nextSecond / 10));
        }
        if(parseInt(nextSecond % 10) != parseInt(second % 10)){
            addBalls(BASE_LEFT + 93 * (circleRadius + 1),BASE_TOP,parseInt(nextSecond % 10));
        }
    }

    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 - circleRadius){
            balls[i].y = WINDOW_HEIGHT - circleRadius;
            balls[i].vy = -balls[i].vy * 0.7;
        }
    }

    var avail = 0;
    for(var i = 0; i < balls.length; i++){
        if(balls[i].x > -circleRadius && balls[i].x < WINDOW_WIDTH + circleRadius){
            balls[avail++] = balls[i];
        }
    }
    while(balls.length > avail){
        balls.pop();
    }
    console.log(balls.length);
}

function addBalls(left,top,num){
    for(var i = 0; i < digit[num].length; i++){
        for(var j = 0; j < digit[num].length; j++){
            if(digit[num][i][j]){
                var aBall = {
                    x:left + 2 * j * (circleRadius + 1) + circleRadius + 1,
                    y:top + 2 * i * (circleRadius + 1) + circleRadius + 1,
                    vx:parseInt(4 + Math.random()*4) * Math.pow(-1,parseInt(Math.random()*1000)),
                    vy:-4,
                    g:3 + parseInt(Math.random()*4-2),
                    color:color[parseInt(Math.random() * color.length)]
                }
                balls.push(aBall);
            }
        }
    }
}

function getCurrShowTimeSeconds(){
    var currTime = new Date();
//    此段为倒计时功能
//    var period = (endTime.getTime() - currTime.getTime())/1000;
//    period = Math.round(period);
//    return period > 0 ? period : 0;
    var result = currTime.getHours()*3600 + currTime.getMinutes()*60 +currTime.getSeconds();
    return result;
}

function drawTime(ctx){
    ctx.clearRect(0,0,WINDOW_WIDTH,WINDOW_HEIGHT);  //刷新画布区域

    var hour = parseInt(currShowTimeSeconds / 3600);
    var minuter = parseInt((currShowTimeSeconds - hour * 3600)/60);
    var second = parseInt(currShowTimeSeconds % 60);
    drawDigit(parseInt(hour / 10), BASE_LEFT, BASE_TOP, ctx);
    drawDigit(parseInt(hour % 10), BASE_LEFT + 15 * (circleRadius + 1), BASE_TOP, ctx);
    drawDigit(10, BASE_LEFT + 30 * (circleRadius + 1), BASE_TOP, ctx);
    drawDigit(parseInt(minuter / 10), BASE_LEFT + 39 * (circleRadius + 1), BASE_TOP, ctx);
    drawDigit(parseInt(minuter % 10), BASE_LEFT + 54 * (circleRadius + 1), BASE_TOP, ctx);
    drawDigit(10, BASE_LEFT + 69 * (circleRadius + 1), BASE_TOP, ctx);
    drawDigit(parseInt(second / 10), BASE_LEFT + 78 * (circleRadius + 1), BASE_TOP, ctx);
    drawDigit(parseInt(second % 10), BASE_LEFT + 93 * (circleRadius + 1), BASE_TOP, ctx);

    for(var i = 0; i < balls.length; i++){
        ctx.beginPath();
        ctx.fillStyle = balls[i].color;
        ctx.arc(balls[i].x,balls[i].y,circleRadius,0,2 * Math.PI,true);
        ctx.fill();
    }
}

function drawDigit(num,x,y,ctx){
    var digitMatrix = digit[num];
    ctx.fillStyle = "#5F4EE9";
    for(var i = 0; i < digitMatrix.length; i++){
        for(var j = 0; j<digitMatrix[i].length; j++){
            if(digitMatrix[i][j]){
                ctx.beginPath();
                var cx = x + 2 * j * (circleRadius + 1) + circleRadius + 1;
                var cy = y + 2 * i * (circleRadius + 1) + circleRadius + 1;
                ctx.arc(cx, cy, circleRadius, 0, 2 * Math.PI, true,ctx);
                ctx.closePath();
                ctx.fill();
            }
        }
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值