1. 案例介绍
最近在慕课网上看了一门有关Canvas的课程案例,炫丽的倒计时效果Canvas绘图与动画基础,学习完这门课程之后,对Canvas绘图有了基本的了解。
2. 案例理解
2.1 如何表示各个数字
引入的 digit.js文件
每一个数字是由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数字的矩阵排列
[
[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函数 获得截止时间到当前时间的秒数.
function getCurrentShowTimeSeconds() {
var curTime = new Date();//当前的时间
var ret = endTime.getTime() - curTime.getTime();
//截止时间的毫秒数-当前时间的毫秒数
ret = Math.round(ret / 1000);//毫秒转化成秒(四舍五入)
return ret >= 0 ? ret : 0;
}
2.3 绘制数字(状态)
render函数 绘制数字的状态
数字由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);
//绘制数字
renderDigit(MARGIN_LEFT, MARGIN_TOP, parseInt(hours / 10), cxt);//绘制小时的十位数
renderDigit(MARGIN_LEFT + 15 * (RADIUS + 1), MARGIN_TOP, parseInt(hours % 10), cxt);//(//绘制小时的个位数)本来是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);//绘制分钟的十位数
renderDigit(MARGIN_LEFT + 54 * (RADIUS + 1), MARGIN_TOP, parseInt(minutes % 10), cxt);//绘制分钟的个位数
renderDigit(MARGIN_LEFT + 69 * (RADIUS + 1), MARGIN_TOP, 10, cxt);//绘制冒号
renderDigit(MARGIN_LEFT + 78 * (RADIUS + 1), MARGIN_TOP, parseInt(seconds / 10), cxt);;//绘制秒数的十位数
renderDigit(MARGIN_LEFT + 93 * (RADIUS + 1), MARGIN_TOP, parseInt(seconds % 10), cxt);;//绘制秒数的个位数
}
2.4 绘制数字(canvas绘制)
renderDigit函数 是完成数字的canvas绘制,遍历digit中指定数字的行列中每一个元素,如果该元素为1,绘制一个小球.
绘制弧,曲线,圆的方法
context.arc(圆的x坐标,圆的y坐标,圆的半径,起始角,结束角,逆时针或顺时针绘图。
false = 顺时针,true = 逆时针,若不填写则默认逆时针)
绘制的小球圆心计算公式
x坐标:x + j * 2 * (RADIUS + 1) + (RADIUS + 1);j表示列数
y坐标:y + i * 2 * (RADIUS + 1) + (RADIUS + 1);i表示行数
function renderDigit(x, y, num, cxt) {//数字最左端横坐标,数字最左端纵坐标,绘制的数字,绘制的上下文环境
cxt.fillStyle = "rgb(0,102,153)";//设置小球的填充颜色
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 每秒更新数字并增加彩色小球
update函数 更新时间并增加小球
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)) {//如果小时的的十位数不同,增加小球
addBalls(MARGIN_LEFT, MARGIN_TOP, parseInt(curHours / 10));
}
if (parseInt(curHours % 10) != parseInt(nextHours % 10)) {//如果小时的的个位数不同,增加小球
addBalls(MARGIN_LEFT + 15 * (RADIUS + 1), MARGIN_TOP, parseInt(curHours % 10));
}
if (parseInt(curMinutes / 10) != parseInt(nextMinutes / 10)) {//如果分钟的的十位数不同,增加小球
addBalls(MARGIN_LEFT + 39 * (RADIUS + 1), MARGIN_TOP, parseInt(curMinutes / 10));
}
if (parseInt(curMinutes % 10) != parseInt(nextMinutes % 10)) {//如果分钟的的个位数不同,增加小球
addBalls(MARGIN_LEFT + 54 * (RADIUS + 1), MARGIN_TOP, parseInt(curMinutes % 10));
}
if (parseInt(curSeconds / 10) != parseInt(nextSeconds / 10)) {//如果秒数的的十位数不同,增加小球
addBalls(MARGIN_LEFT + 78 * (RADIUS + 1), MARGIN_TOP, parseInt(curSeconds / 10));
}
if (parseInt(curSeconds % 10) != parseInt(nextSeconds / 10)) {//如果秒数的的个位数不同,增加小球
addBalls(MARGIN_LEFT + 93 * (RADIUS + 1), MARGIN_TOP, parseInt(nextSeconds % 10));
}
curShowTimeSeconds = nextShowTimeSeconds;
}
updateBalls();
}
addBalls函数 增加彩色小球,定义每个彩色小球的属性,并且把生成的彩色小球放在balls数组中.
- x (圆心x坐标)
- y (圆心纵坐标)
- 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函数 更新小球的位置,碰撞检测以及控制小球数量
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摩擦系数,使得小球的弹跳更符合实际
}
}
//控制小球数量
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();//删除病返回数组最后一个元素
}
}