1. 程式人生 > >用canvas完成超炫酷的數字時鐘

用canvas完成超炫酷的數字時鐘

 <canvas id="canvas" style="height:100%">

這次要完成的是一個很炫酷很炫酷的時鐘,

如圖

 

每一次時鐘更新都伴隨有很多彩色小球落下,很不是很贊啊!O(∩_∩)O~

這個東西呢,主要用到了兩個方面的東西:canvas和js中的Date物件,

涉及到的canvas屬性和方法有:

Canvas的宣告

        當前瀏覽器不支援Canvas,請更換瀏覽器後再試

</canvas>

設定canvas畫布的寬高:

canvas.width = WINDOW_WIDTH;
    canvas.height = 600;

以及canvas中的context繪圖以及其方法:

<span style="white-space:pre">	</span>var canvas = document.getElementById('canvas');
    <span style="white-space:pre">	</span>var context = canvas.getContext("2d");
 <span style="white-space:pre">	</span>cxt.beginPath();
        cxt.arc( balls[i].x , balls[i].y , RADIUS , 0 , 2*Math.PI , true );
        cxt.closePath();
 
        cxt.fill();


Date物件涉及到有:

宣告:

var curtime=new Date();

getHours().getMinutes(),getSeconds(),getTimes()等方法

下面就一步一步說一下是如何完成這個實驗的,

①首先應該完成的就是如何用點陣的方式講時間繪製出來,

對於這種繪製圖像的東西如果是做過俄羅斯方塊等小遊戲的同學都知道,這都是用陣列完成,在陣列對應的地方完成點的繪製,進而完成整個數字,時鐘的每個數字就是這樣設計的,我們用的是三維陣列,主要是為了方便索引,下面就是2和6的點陣

[
            [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
[
            [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

是不是很形象啊,然後我們在繪製整個數字的時候只需要遍歷對應的陣列就ok啦

Canvas繪製圓形的方法:

Canvas提供了很多畫圖形的方法,這裡介紹畫直線和畫圓的方法,

直線:

context.strokeStyle='#000';//線條顏色
context.lineWidth=10;//線寬

context.beginPath();//開始路徑
context.moveTo(20,0);//起始點
context.lineTo(100,0);//結束點
context.stroke();//畫線,一定要記住執行這個方法才會畫圖
context.closePath();//結束路徑


畫圓:

cxt.arc( x , y , radius , 0 , 2*Math.PI ,true),

xy對應的是圓心座標,所有的座標都是針對畫布的左上角來的x軸和有軸,radius半徑,0和2*Math.PI圓的起始和結束點,因為圓是2π,所以0到2π就是整個圓,也可以是半圓π等,true是否逆時針畫圖,預設false

我們現在先設定時間為12:34 : 56方便我們先實現時鐘,

那麼現在只需要對應的數字,然後傳遞到方法裡面,到數組裡面檢索,畫出來就可以辣

function renderDo( x , y , num , cxt ){
 
    cxt.fillStyle = "rgb(184,72,255)";
 
    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 )
                cxt.closePath()
 
                cxt.fill()
            }
}


④上面只是一個數字的繪製,那麼多個數字間距怎麼控制呢,其實也是很簡單的,

我們先設定一個預定的margin-left 和margin-top,以及每個點的半徑

var RADIUS = 8;
var MARGIN_TOP = 60;
var MARGIN_LEFT = 30;


那麼123456中2距離1應該是多遠呢,我們可以注意到有固定的radius,那麼2的中心位置應該在MARGIN_LEFT + 15*(RADIUS+1)這個位置,為什麼是15呢,我們的陣列是7列的。也就是14*(radius+1),再留出1的間距,就是15倍咯,那麼接下來的每個數字就簡單了

renderDo( MARGIN_LEFT , MARGIN_TOP , parseInt(hours/10) , cxt )
     renderDo( MARGIN_LEFT + 15*(RADIUS+1) , MARGIN_TOP , parseInt(hours%10) , cxt )
     renderDo( MARGIN_LEFT + 30*(RADIUS + 1) , MARGIN_TOP , 10 , cxt )
     renderDo( MARGIN_LEFT + 39*(RADIUS+1) , MARGIN_TOP , parseInt(minutes/10) , cxt);
     renderDo( MARGIN_LEFT + 54*(RADIUS+1) , MARGIN_TOP , parseInt(minutes%10) , cxt);
     renderDo( MARGIN_LEFT + 69*(RADIUS+1) , MARGIN_TOP , 10 , cxt);
     renderDo( MARGIN_LEFT + 78*(RADIUS+1) , MARGIN_TOP , parseInt(seconds/10) , cxt);
     renderDo( MARGIN_LEFT + 93*(RADIUS+1) , MARGIN_TOP , parseInt(seconds%10) , cxt);


這裡只需要注意x,y的值就可以了,其他的先不管。

現在就應該是這個效果了

⑤靜止的東西拿出來好意思嗎對不對,那麼我們要讓他動起來怎麼玩?

Js裡有倆個定時重新整理的函式:setInterval和setTimeout,運用就可以是畫布定時重新整理了

//50ms重新整理一次
    setInterval(
        function(){
//匿名函式
        }
        ,
        50
    );

現在時鐘已經是基本完成了,那麼剩下的就是在頁面上新增小球了,那麼就有以下這些問題,如何實現數字更新的時候新增小球,小球掉落如何實現,怎麼看起來自然:

以上這些問題呢,我也想了一陣子,後來得別人啟發才明白的,

我存一個當前時間的引數值,然後與下一刻的引數值比較(因為一直在setInterval),如果不等就說明時間更新了,那麼就要新增小球了。

var nextShowTimeSeconds = getCurrentShowTimeSeconds();
 
    var nextHours = parseInt( nextShowTimeSeconds / 3600);
    var nextMinutes = parseInt( (nextShowTimeSeconds - nextHours * 3600)/60 )
    var nextSeconds = nextShowTimeSeconds % 60
 
    var curHours = parseInt( curShowTimeSeconds / 3600);
    var curMinutes = parseInt( (curShowTimeSeconds - curHours * 3600)/60 )
    var curSeconds = curShowTimeSeconds % 60
 
    if( nextSeconds != curSeconds ){
        if( parseInt(curHours/10) != parseInt(nextHours/10) ){
            addBalls( MARGIN_LEFT + 0 , MARGIN_TOP , parseInt(curHours/10) );
        }
}

接下來就是實現新增小球了,其實這個真沒什麼說的,在陣列中找到數字的座標相應的算一下距離就找到位置了,如何讓他看起來更自然的下落呢,這個就要模擬自由落體了,

設定x,y的速度vx和vy以及加速度g,按照物理的實現就行了

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),
                    g:1.5+Math.random(),
                    vx:Math.pow( -1 , Math.ceil( Math.random()*1000 ) ) * 4,
                    vy:-5,
                    color: colors[ Math.floor( Math.random()*colors.length ) ]
                }
 
                balls.push( aBall )
            }
}


⑦ 這樣的話,那麼小球到達底部的時候就應該彈起來的才對啊,而且速度也應該減少啊,判斷一下不就行了嗎:

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

小球的繪製:

跟之前的數字的繪製是類似的,只不過位置在變化而已。

fillStyle填充色,

只有呼叫了fill()方法(和stroke一樣)才會真正繪製到畫布上

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();
    }


到這裡功能差不多都實現了,但是還存在一個很大的問題,那就是心細的同學已經注意到了,我拿來裝小球的陣列,如果按照上邊的方法就是一直在增加,沒有減少,這會使記憶體佔用越來越多,執行久了就會崩掉,那麼如何讓實現移除陣列中沒有在畫布上的小球呢,好伐,這個我覺得是最坑的地方,簡簡單單的出了外邊界就移除是不合理的,後來老師給我們講了一個很好很技巧的方法。

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


在每一次更新,對球的陣列進行一次更新,怎麼更新呢?這就是我覺得老師最厲害的地方了,在這個實現中因為滿足移除條件的球絕對比所有的球少,那麼cnt的索引一定比i小,

這樣就會(儘量)使在頁面的點靠前,而頁面外的點靠後,那麼只需要移除cnt這個位置之後的點就可以了,,由於快取的問題,還是會導致縮小瀏覽器等會開啟會導致頁面積累很多點

 開啟之後一會又自己消掉了,這一點我也不要明白,但是我們設定一個數組上限也可以解決了

while( balls.length > Math.min(300,cnt) ){
        balls.pop();
    }

理解了之後才發現這個真的好技巧,一定是自己資料結構沒學好啊,

最後感謝老師liuyubobobo授課。

最後貼上整個實現包的下載地址炫麗時鐘。O(∩_∩)O~