1. 案例介紹
最近在慕課網(wǎng)上看了一門有關(guān)Canvas的課程案例,炫麗的倒計時效果Canvas繪圖與動畫基礎(chǔ),學(xué)習(xí)完這門課程之后,對Canvas繪圖有了基本的了解。
效果展示.gif
2. 案例理解
2.1 如何表示各個數(shù)字
引入的 digit.js文件
每一個數(shù)字是由10行7列的0,1點陣表示,冒號是由10行4列的0,1點陣表示,1的位置表示小球。
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
......//從0-9數(shù)字的矩陣排列
[
[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]
]//第10個元素表示冒號
]
2.2 倒計時的表示
getCurrentShowTimeSeconds函數(shù) 獲得截止時間到當(dāng)前時間的秒數(shù).
function getCurrentShowTimeSeconds() {
var curTime = new Date();//當(dāng)前的時間
var ret = endTime.getTime() - curTime.getTime();
//截止時間的毫秒數(shù)-當(dāng)前時間的毫秒數(shù)
ret = Math.round(ret / 1000);//毫秒轉(zhuǎn)化成秒(四舍五入)
return ret >= 0 ? ret : 0;
}
2.3 繪制數(shù)字(狀態(tài))
render函數(shù) 繪制數(shù)字的狀態(tài)
數(shù)字.png
數(shù)字由10行7列的方格組成,小球半徑為 R, 方格半徑為 R+1
function render(cxt) {
cxt.clearRect(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT);//刷新掉原來的圖像,否則新圖像會疊加在上面
var hours = parseInt(curShowTimeSeconds / 3600);
var minutes = parseInt((curShowTimeSeconds - hours * 3600) / 60);
var seconds = parseInt(curShowTimeSeconds % 60);
//繪制數(shù)字
renderDigit(MARGIN_LEFT, MARGIN_TOP, parseInt(hours / 10), cxt);//繪制小時的十位數(shù)
renderDigit(MARGIN_LEFT + 15 * (RADIUS + 1), MARGIN_TOP, parseInt(hours % 10), cxt);//(//繪制小時的個位數(shù))本來是14個(R+1),為了有點空隙,取15個
renderDigit(MARGIN_LEFT + 30 * (RADIUS + 1), MARGIN_TOP, 10, cxt);//冒號只有4列, 8個(R+1),多取一個9個(R+1)
renderDigit(MARGIN_LEFT + 39 * (RADIUS + 1), MARGIN_TOP, parseInt(minutes / 10), cxt);//繪制分鐘的十位數(shù)
renderDigit(MARGIN_LEFT + 54 * (RADIUS + 1), MARGIN_TOP, parseInt(minutes % 10), cxt);//繪制分鐘的個位數(shù)
renderDigit(MARGIN_LEFT + 69 * (RADIUS + 1), MARGIN_TOP, 10, cxt);//繪制冒號
renderDigit(MARGIN_LEFT + 78 * (RADIUS + 1), MARGIN_TOP, parseInt(seconds / 10), cxt);;//繪制秒數(shù)的十位數(shù)
renderDigit(MARGIN_LEFT + 93 * (RADIUS + 1), MARGIN_TOP, parseInt(seconds % 10), cxt);;//繪制秒數(shù)的個位數(shù)
}
2.4 繪制數(shù)字(canvas繪制)
renderDigit函數(shù) 是完成數(shù)字的canvas繪制,遍歷digit中指定數(shù)字的行列中每一個元素,如果該元素為1,繪制一個小球.
繪制弧,曲線,圓的方法
context.arc(圓的x坐標(biāo),圓的y坐標(biāo),圓的半徑,起始角,結(jié)束角,逆時針或順時針繪圖。
false = 順時針,true = 逆時針,若不填寫則默認(rèn)逆時針)
圖片.png
繪制的小球圓心計算公式
x坐標(biāo):x + j * 2 * (RADIUS + 1) + (RADIUS + 1);j表示列數(shù)
y坐標(biāo):y + i * 2 * (RADIUS + 1) + (RADIUS + 1);i表示行數(shù)
function renderDigit(x, y, num, cxt) {//數(shù)字最左端橫坐標(biāo),數(shù)字最左端縱坐標(biāo),繪制的數(shù)字,繪制的上下文環(huán)境
cxt.fillStyle = "rgb(0,102,153)";//設(shè)置小球的填充顏色
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();
}
}
}
}
2.5 每秒更新數(shù)字并增加彩色小球
update函數(shù) 更新時間并增加小球
function update() {
var nextShowTimeSeconds = getCurrentShowTimeSeconds();
var nextHours = parseInt(nextShowTimeSeconds / 3600);
var nextMinutes = parseInt((nextShowTimeSeconds - nextHours * 3600) / 60);
var nextSeconds = parseInt(nextShowTimeSeconds % 60);
var curHours = parseInt(curShowTimeSeconds / 3600);
var curMinutes = parseInt((curShowTimeSeconds - curHours * 3600) / 60);
var curSeconds = parseInt(curShowTimeSeconds % 60);
if (nextSeconds != curSeconds) {//時間變化時,增加彩色小球
if (parseInt(curHours / 10) != parseInt(nextHours / 10)) {//如果小時的的十位數(shù)不同,增加小球
addBalls(MARGIN_LEFT, MARGIN_TOP, parseInt(curHours / 10));
}
if (parseInt(curHours % 10) != parseInt(nextHours % 10)) {//如果小時的的個位數(shù)不同,增加小球
addBalls(MARGIN_LEFT + 15 * (RADIUS + 1), MARGIN_TOP, parseInt(curHours % 10));
}
if (parseInt(curMinutes / 10) != parseInt(nextMinutes / 10)) {//如果分鐘的的十位數(shù)不同,增加小球
addBalls(MARGIN_LEFT + 39 * (RADIUS + 1), MARGIN_TOP, parseInt(curMinutes / 10));
}
if (parseInt(curMinutes % 10) != parseInt(nextMinutes % 10)) {//如果分鐘的的個位數(shù)不同,增加小球
addBalls(MARGIN_LEFT + 54 * (RADIUS + 1), MARGIN_TOP, parseInt(curMinutes % 10));
}
if (parseInt(curSeconds / 10) != parseInt(nextSeconds / 10)) {//如果秒數(shù)的的十位數(shù)不同,增加小球
addBalls(MARGIN_LEFT + 78 * (RADIUS + 1), MARGIN_TOP, parseInt(curSeconds / 10));
}
if (parseInt(curSeconds % 10) != parseInt(nextSeconds / 10)) {//如果秒數(shù)的的個位數(shù)不同,增加小球
addBalls(MARGIN_LEFT + 93 * (RADIUS + 1), MARGIN_TOP, parseInt(nextSeconds % 10));
}
curShowTimeSeconds = nextShowTimeSeconds;
}
updateBalls();
}
addBalls函數(shù) 增加彩色小球,定義每個彩色小球的屬性,并且把生成的彩色小球放在balls數(shù)組中.
- x (圓心x坐標(biāo))
- y (圓心縱坐標(biāo))
- g (重力加速度)
- vx (水平方向的速度)
- vy (垂直方向的速度)
- color (小球的顏色)
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,//[-4,4]
vy: -5,
color: colors[Math.floor(Math.random() * colors.length)]
};
balls.push(aBall);
}
}
}
}
2.6 更新小球
updateBalls函數(shù) 更新小球的位置,碰撞檢測以及控制小球數(shù)量
function updateBalls() {
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;//0.75摩擦系數(shù),使得小球的彈跳更符合實際
}
}
//控制小球數(shù)量
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];//cnt <= i,前cnt個小球符合要求,cnt后面的小球可以刪掉
}
}
while(balls.length > cnt) {
balls.pop();//刪除病返回數(shù)組最后一個元素
}
}