Canvas倒计时效果预览图
实现难点
1、如何用小球把数字绘制出来
我们把数字变为了一个里面只有0和1的数组,1就代表小球出现的位置。如下所示。
将0-9十个数组组成的大数组命名为Digit
,存储在digit.js
文件中,之后直接在index
文件中引用。
// digit.js
var 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
]
2、绘制数字时遇到半径和距离相关的问题
首先,距离是需要静下心来慢慢算的。最好能在纸上先画出来。在这里我提供一点小建议。
- 将半径、数字与数字之间的间隔、圆点之间的间隔设为全局变量,方便修改而且更容易理清关系。
var Radius = 9;
var Gap = 20;
- 绘制函数
context.arc(x, y, r, start, end)
中的x
与y
指的是圆心的坐标,所以一定要记住处理边缘关系时还要考虑到半径。
3、渲染函数功能划分
1、
// 单个数字绘制函数
// x:绘制起点横坐标
// y: 绘制起点纵坐标
// num: 绘制的数字
// cxt:canvas绘制上下文
function renderDigit(x, y, num, cxt) { }
2、
// 渲染秒
function renderSeconds(cxt) { }
// 渲染分
function renderMinutes(cxt) { }
// 渲染时
function renderHours(cxt) { }
3、
// 总体渲染函数
function render(cxt) { }
4、随着当前时间变化
通过new Date()
的getHours//getMinutes//getSeconds
获取到当前时间的时分秒,再重新渲染一遍。
Canvas的渲染速度很快,所以每次循环都可以清空画布然后再重头开始渲染,自然可以看到最新的时间变化。
render()
中调用renderHours
等函数,renderHours
中再调用new Date()
。
setInterval(() => {
render();
}, 50)
5、单个小球的运动
首先定义小球的属性
var ball = {
x: 圆心横坐标,
y: 圆心纵坐标,
r: 半径,
vx: 小球的横向运动速度,
vy: 小球的纵向运动速度,
g: 加速度,
color: 小球的填充颜色
}
绘制出小球与上面的单个数字小球的绘制方法一样,这里详细讲讲小球的运动实现过程。
function updateBall() {
ball.x += ball.vx;
ball.y += ball.vy;
ball.vy += ball.g;
// 小球掉到画布下边缘时
if(ball.y >= WINDOW_HEIGHT - ball.r) {
ball.vy = -ball.vy * Rub; // 小球获得向上的速度,Rub是摩擦系数
ball.y = WINDOW_HEIGHT - ball.r;
}
};
// 接着用定时器循环调用方法,可实现动画的效果。
setInterval(() => {
updateBall();
renderBall();
}, 50)
6、获取小球所在位置
在renderDigit()
函数中返回一组渲染出来的小球数组。
// 首先定义全局变量Balls,用于存储返回的小球
var Balls = [];
function renderDigit(x, y, num, cxt) {
// 每渲染一个数字都会返回一组全新的小球位置数组
Balls = [];
var numDigit = Digit[parseInt(num)];
for(let i=0; i<numDigit.length; i++) {
for(let j=0; j<numDigit[i].length; j++) {
if(numDigit[i][j] == 1) {
cxt.beginPath();
cxt.arc(x+(Gap*j), y+(Gap*i), Radius, 0, 2*Math.PI);
cxt.fillStyle = '#fff';
cxt.fill();
Balls.push({
'x': x+(Gap*j),
'y': y+(Gap*i),
'r': Radius + Math.random(),
'vx': Math.pow( -1 , Math.ceil( Math.random()*1000 ) ) * 4,
'vy': VY + Math.random()*4,
'g': ASpeed + Math.random(),
'color': Colors[parseInt(Colors.length*Math.random())]
})
}
}
};
}
7、当数字发生变化时产生相应的小球,以及存储需要动画的小球
首先设置一个数组OldNums
用来放旧的数字,用于对比数字是否发生变化。数组一共有六个元素,分别对应着时分秒的六位数字。
还要设置一个全局变量foreverBalls
,用于存放当前页面上需要产生运动轨迹的小球参数,之后只需要一直循环渲染这个数组就可以。
var OldNums = []; // length = 6
var foreverBalls = [];
renderDigit(Left+Radius+Gap*21+Gap*21, Radius, parseInt(seconds/10), cxt);
// 在更新数组前先作数字是否发生变化的对比
if(oldNums[4] != parseInt(seconds/10) {
// 对小球数组Balls操作
foreverBalls.push(Balls);
}
// 在渲染时分秒的函数最后,更新数组
oldNums[4] = parseInt(seconds/10);
8、多个小球在页面上产生运动轨迹
在原有的render
函数中循环foreverBalls
并渲染所有小球,然后在定时器中调用updateBall()
方法。
setInterval(() => {
render(cxt);
updateBubble();
}, 50)
9、优化
foreverBalls
数组一直被添加小球,所以需要渲染的小球就会越来越多,所以我们需要当小球数量过多时,删除掉一些看不到但一直在运动的小球。
//防止数组中球的数量太多
function clearBall() {
if(foreverBall.length>60) {
foreverBall.splice(0, 30);
}
}
- 页面最小化到后台会产生堆积小球的情况,未解决。