項目演示git
此項目是慕課網上的視頻,這篇文章本身的學習總結。github
在canvas
中繪製數字,可使用點陣的方式。canvas
下面是5
的點陣佈局,1
表明要繪製,0
表明不要繪製。這裏數字採用的是10 * 7
的網格系統,冒號是10 * 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] ]
有了這個這個網格的系統,咱們就能將數字繪製出來了。dom
在canvas
中如何用圓將數字繪製出來呢?,圓心的x
軸座標,y
座標如何肯定?函數
ctx.arc( x + j * 2 * (radius + 1) + (radius + 1), y + i * 2 * (radius + 1) + (radius + 1), radius, 0, 2 * Math.PI )
假設圓的半徑爲R
佈局
R
,包圍盒的方框爲R+1
,因此每一個方框的邊長就是2*(R+1)
(x,y)
,i
表示行,j
表示列因此第(i,j)
位置的座標是:學習
x
軸座標: x + j*2*(R+1)+(R+1)
y
軸座標: y + i*2*(R+1)+(R+1)
這裏面x + j*2*(R+1)
是繪製到包圍盒的前面,在加上(R+1)
,使得繪製的點在圓心。優化
第二個數字的起始位置怎麼肯定?動畫
marginLeft
加上第一個數字的位置。咱們的數字點陣是7
列,一個數字的佔的空間就是14*(R+1)
,爲了在兩個數字間空出點的距離,因此就用了15
這要注意第三個位置是冒號,而咱們冒號使用的是10 * 4
的點陣系統,因此在繪製第四個數字時,加上的是9
而不是15
。
renderDigit(marginLeft, marginTop, parseInt(hour / 10), ctx); renderDigit(marginLeft + 15 * (radius + 1), marginTop, parseInt(hour % 10), ctx); renderDigit(marginLeft + 30 * (radius + 1), marginTop, 10, ctx); renderDigit(marginLeft + 39 * (radius + 1), marginTop, parseInt(minutes / 10), ctx); renderDigit(marginLeft + 54 * (radius + 1), marginTop, parseInt(minutes % 10), ctx); renderDigit(marginLeft + 69 * (radius + 1), marginTop, 10, ctx); renderDigit(marginLeft + 78 * (radius + 1), marginTop, parseInt(seconds / 10), ctx); renderDigit(marginLeft + 93 * (radius + 1), marginTop, parseInt(seconds % 10), ctx);
怎麼樣在屏幕上顯示出時間在動的效果呢?
nextShowTimeSeconds
nextSeconds
不等於curSeconds
,說明時間變化了,須要更新當前時間。let nextShowTimeSeconds = getCurrentShowTimeSeconds(); let nextHour = parseInt(nextShowTimeSeconds / 3600); let nextMinutes = parseInt((nextShowTimeSeconds - nextHour * 3600) / 60); let nextSeconds = nextShowTimeSeconds % 60; let curHour = parseInt(curShowTimeSeconds / 3600); let curMinutes = parseInt((curShowTimeSeconds - curHour * 3600) / 60); let curSeconds = curShowTimeSeconds % 60; if (nextSeconds !== curSeconds) { curShowTimeSeconds = nextShowTimeSeconds; }
時間已經動起來了,在時間發生變化的時候,如何讓在時間的位置出現多彩小球呢?
在時間放生變化的時候,讓多彩小球出如今頁面中。
這裏就須要對每一個時間都進行判斷,在時間發生變化的時候,調用addBall
函數
if (parseInt(curHour / 10) !== parseInt(nextHour / 10)) { addBall(marginLeft, marginTop, parseInt(curHour / 10)); } if (parseInt(curHour % 10) !== parseInt(nextHour % 10)) { addBall(marginLeft + 15 * (radius + 1), marginTop, parseInt(curHour % 10)); } if (parseInt(curMinutes / 10) !== parseInt(nextMinutes / 10)) { addBall(marginLeft + 39 * (radius + 1), marginTop, parseInt(curMinutes / 10)); } if (parseInt(curMinutes % 10) !== parseInt(nextMinutes % 10)) { addBall(marginLeft + 54 * (radius + 1), marginTop,parseInt(curMinutes % 10)); } if (parseInt(curSeconds / 10) !== parseInt(nextSeconds / 10)) { addBall(marginLeft + 78 * (radius + 1), marginTop, parseInt(curSeconds / 10)); } if (parseInt(curSeconds % 10) !== parseInt(nextSeconds % 10)) { addBall(marginLeft + 93 * (radius + 1), marginTop, parseInt(curSeconds % 10)); }
addBall
addBall
函數是對小球在指定位置進行渲染
addBall
函數接收三個參數,小球的x
座標,y
座標以及數字。
在此遍歷全部數字,小球若是是1
就須要再該位置繪製,並添加小球balls
裏。
function addBall(x, y, num) { digit[num].forEach((element, i) => { element.forEach((item, j) => { let aBall = {}; if (item === 1) { 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, //前面一段是取 -1 仍是 1 vy: -5, color: colors[Math.floor(Math.random() * colors.length)] }; } balls.push(aBall); }); }); }
aball
是要繪製在屏幕的小球,它接收7
個參數:
x
: x
座標y
: Y
座標r
: 半徑g
: 加速度vx
: x
軸速度vy
: y
軸速度color
: 顏色這裏重點說下g
,vx
,vy
這三個屬性的做用,這裏用到的實際上是初中物理知識——速度和加速度。
ball = {x:512, y:100, r:20, g:1.5, vx:-4, vy:0, color:'#058'}
小球繪製完以後,須要讓小球有個落地的效果
對小球的數組balls
進行比遍歷,讓每一個小球的x
,y
座標分別加上在本身軸上的運動速度,y
軸速度vy
要加上小球的加速度。這樣就有一個小球落地的效果了。
balls.forEach(ball => { ball.x += ball.vx; ball.y += ball.vy; ball.vy += ball.g; });
仍是在balls
遍歷的數組裏面。
如何知道小球有沒落地呢?
檢測小球的y
軸座標,若是小球y
軸座標大於等於 屏幕的高度減去小球半徑,就說明小球已經觸底了。須要有一個回彈的效果。
回彈的時候受空氣阻力的影響有的阻力系數。
更新小球的y
座標和y
軸的速度,小球就會有一個回彈的效果。
if (ball.y >= windowHeight - radius) { ball.y = windowHeight - radius; ball.vy = -ball.vy * 0.6; }
這個程序行運行一段時間後,會發現特別卡頓。這是由於咱們一直在添加小球,而沒有刪除小球,
console.log(balls.length)
在更新小球的時候,打印balls
的長度,就會發現一直在不斷的增長,無論你電腦內存有多大,最後都會被佔用光,因此必須得對balls
數組的長度進行限制。
這裏的優化技巧就是,檢測小球是否在屏幕內,若是不在屏幕內,就將它刪除。
let cnt = 0; balls.forEach(ball => { if (ball.x + radius > 0 && ball.x - radius < windowWidth) { balls[cnt++] = ball; } });
如何檢測小球是否在屏幕內進行檢測呢?
仍是使用上面檢測落地的方法。
小球的x
座標 +
小球的半徑 >
0
而且 小球的x
座標 - 小球的半徑 <
畫布半徑,說明小球在畫布裏面。
使用cnt
進行計數,將符合要求(在屏幕內)的小球,放到balls
的前面
那麼前cnt
個小球都是也畫布中的,只須要將cnt
後的小球進行刪除就好了。
while (balls.length > Math.min(300, cnt)) { balls.pop(); }
Math.min(300, cnt))
的意思是,若是cnt > 300
,取300
,不然取cnt
。
它的做用是控制屏幕中效果的個數。
作這個動畫的最大收穫是: