絢麗的時鐘效果學習總結

一、背景

本文是在學習慕課網的“絢麗的倒計時效果-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();
            }
        }
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章