您的位置:首页 > Web前端 > HTML

绚丽的时钟效果学习总结

2016-01-23 15:59 501 查看

一、背景

本文是在学习慕课网的“绚丽的倒计时效果-canvas绘图与动画基础”后总结而成。

每次变换就有对应的球掉落,成品效果图如下:



二、学习收获

以像素来构图的思路

本例中以二维矩阵来显示每一个数字,使用一个7*10的点阵,1表明画圆,0不画,最后组成每个数字,组成的方式也值得学习,也还可以换其他风格的数字,字母等。

屏幕自适应处理

需做的工作:

a).在html、body、canvas中增加样式height:100%,

b).使用document.body的clientHeight和clientWidth方法取到body的宽高。

c).规划好左、右、上的空白比例,以此计算每个数字的起始显示位置和小球的半径。

由上可知,自适应的一种策略可以是:先以宏定义宽高,在某一个屏幕大小调好其他的效果后,最后根据需要的布局从外到内的计算具体的尺寸。

动画基础

setInterval(func,interval)方法定义定时器,每间隔interval调用func函数。

func函数中做两件事:一是重绘画布;二是更新数据,指定在重绘下一帧时使用的数据。

物理过程模拟

ball = {x:40,y:50,vx:4,vy:2,g:3}定义小球的起始位置x、y,水平和垂直vx、vy,垂直加速度g

每次更新数据时x = x + vx; y = y + vy; vy = vy + g;

碰撞检测:当x > height - radio 时,vy = - vy * a; //height为画布宽度,radio为球半径,a为系数,保证反弹后最大高度降低。

性能优化

在检测到水平方向球在屏幕外时,从balls数组中移除元素的方法,很是值得借鉴:

var avail = 0;

for(var i = 0; i < balls.length; i++){

if(balls[i].x > -circleRadius && balls[i].x < WINDOW_WIDTH + circleRadius){

balls[avail++] = balls[i];

}

}

从前向后检测,合法就移到数组前面,最后avail就是检测合格,需要保留的元素,之后可以使用:

while(balls.length > avail){

balls.pop();

}

调整数组,也可以简单的使用balls.length = avail 达到相同的缩减数组的效果。

原教程中对最终数组长度有这样的优化:取Math.max(400,avail); 400为某一实践值,但我觉得这样不好,会使动画不连续。

针对此处,有自己做过的一些改进:

原代码parseInt(Math.random()4) Math.pow(-1,parseInt(Math.random()*1000))的问题在于,使用的vx为从0开始的随机数,范围±4,这里会引入一个问题,请看如下分析:

假设球的水平速度均为v,画布宽度为w,因为每秒都会变换秒针,即使只考虑每秒只变一位数,每次只增加10+7=17个球,场内球数量达到平衡时总数有17*w/v个球

因为是随机,当v随到0或接近0时,场内球数仍为无穷大,所以即使将越界的球删除了,但球的个数还是会无限的增加下去,针对此情况,可将水平方向的数组这样设置:parseInt(3 + Math.random()4) Math.pow(-1,parseInt(Math.random()*1000)),此时速度范围为(-7,-3] 与[3,7),正负只是决定了从左还是右出屏幕,速度的绝对值才是影响存在屏幕上的球数量的关键点,此时速度最小为3,即场上球数量最多为17*w/3,当然需要指出的是,这个数值不严谨,考虑到极端情况6位数字都变化,也只是在此数值上乘以6,数量级上相差不大,而且极端情况也不会一直发生。

canvas的api

此部分最重要的思想就是画图是分构思和执行两步的,strokeStyle,fillStyle,moveTo,lineTo,lineWidth,arc等都是在调整画笔的状态(位置,颜色,宽度,弧线),只有在stroke和fill方法使用后才是真正画到画布上。

beginPath、closePath、clearRact等方法则是清楚画笔之前的状态,避免画笔前后状态冲突的方法。

三、完整代码

附上最后可工作的代码:

主体的Timer.html文件:

<!DOCTYPE html>
<html style="height:98%;margin:0">
<head lang="en">
<meta charset="UTF-8">
<title>Timer</title>
</head>
<body style="height:100%">
<canvas id="canvas" style="height:100%;margin:0">
浏览器不支持canvas
</canvas>
</body >
<script type="text/javascript" src="digit.js"></script>
<script type="text/javascript" src="timer.js"></script>
</html>


用于表示0-9以及冒号的点阵数据digit.js:

var digit = [
[                               //0
[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]
],[                             //1
[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]
],[                             //2
[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]
],[                             //3
[1, 1, 1, 1, 1, 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, 0, 0, 0, 1, 1, 0],
[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, 0, 0]
],[                             //4
[0, 0, 0, 0, 1, 1, 0],
[0, 0, 0, 1, 1, 1, 0],
[0, 0, 1, 1, 1, 1, 0],
[0, 1, 1, 0, 1, 1, 0],
[1, 1, 0, 0, 1, 1, 0],
[1, 1, 1, 1, 1, 1, 1],
[0, 0, 0, 0, 1, 1, 0],
[0, 0, 0, 0, 1, 1, 0],
[0, 0, 0, 0, 1, 1, 0],
[0, 0, 0, 1, 1, 1, 1]
],[                             //5
[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]
],[                             //6
[0, 0, 0, 0, 0, 1, 1],
[0, 0, 0, 1, 1, 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]
],[                             //7
[1, 1, 1, 1, 1, 1, 1],
[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, 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]
],[                             //8
[0, 1, 1, 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],
[0, 1, 1, 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],
],[                             //9
[0, 1, 1, 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],
[0, 1, 1, 1, 0, 1, 1],
[0, 0, 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, 1, 1, 0, 0, 0, 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],
[0, 1, 1, 0],
[0, 1, 1, 0],
[0, 0, 0, 0],
[0, 0, 0, 0]
]
];


最后是主要的画图逻辑timer.js

var WINDOW_WIDTH = 1024;
var WINDOW_HEIGHT = 768;
var BASE_LEFT = 50;
var BASE_TOP = 50;
var circleRadius = 7;

const endTime = new Date(2016,0,23,23,05,00);
var currShowTimeSeconds = 0;

var balls =[];
var color = ['#C34EAB','#6C4E8A','#D6F86D','#7CF82F','#F73485','#6ACAE5','#F4CAD9','#4D085E','#ED977F','#CEE5DD'];
window.onload = function () {
WINDOW_HEIGHT = document.body.clientHeight;
WINDOW_WIDTH = document.body.clientWidth;

BASE_LEFT = Math.round(WINDOW_WIDTH / 10);
circleRadius = Math.round(WINDOW_WIDTH * 4 / 5 / 108) - 1;
BASE_TOP = Math.round(WINDOW_HEIGHT / 5);

var canvas = document.getElementById('canvas');
canvas.width = WINDOW_WIDTH;
canvas.height = WINDOW_HEIGHT;
var context = canvas.getContext('2d');
currShowTimeSeconds = getCurrShowTimeSeconds();
setInterval(function(){
drawTime(context)
bda2
;
update();
},50);
drawTime(context);
}

function update(){
var nextShowTimeSeconds = getCurrShowTimeSeconds();
var nextHour = parseInt(nextShowTimeSeconds / 3600);
var nextMinuter = parseInt((nextShowTimeSeconds - nextHour * 3600) / 60);
var nextSecond = parseInt(nextShowTimeSeconds % 60);

var hour = parseInt(currShowTimeSeconds / 3600);
var minuter = parseInt((currShowTimeSeconds - hour * 3600)/60);
var second = parseInt(currShowTimeSeconds % 60);
if(second != nextSecond){
currShowTimeSeconds = nextShowTimeSeconds;
if(parseInt(nextHour / 10) != parseInt(hour / 10)){
addBalls(BASE_LEFT,BASE_TOP,parseInt(nextHour / 10));
}
if(parseInt(nextHour % 10) != parseInt(hour % 10)){
addBalls( BASE_LEFT + 15 * (circleRadius + 1),BASE_TOP,parseInt(nextHour % 10));
}
if(parseInt(nextMinuter / 10) != parseInt(minuter / 10)){
addBalls(BASE_LEFT + 39 * (circleRadius + 1),BASE_TOP,parseInt(nextMinuter / 10));
}
if(parseInt(nextMinuter % 10) != parseInt(minuter % 10)){
addBalls(BASE_LEFT + 54 * (circleRadius + 1),BASE_TOP,parseInt(nextMinuter % 10));
}
if(parseInt(nextSecond / 10) != parseInt(second / 10)){
addBalls(BASE_LEFT + 78 * (circleRadius + 1),BASE_TOP,parseInt(nextSecond / 10));
}
if(parseInt(nextSecond % 10) != parseInt(second % 10)){
addBalls(BASE_LEFT + 93 * (circleRadius + 1),BASE_TOP,parseInt(nextSecond % 10));
}
}

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

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

function addBalls(left,top,num){
for(var i = 0; i < digit[num].length; i++){
for(var j = 0; j < digit[num].length; j++){
if(digit[num][i][j]){
var aBall = {
x:left + 2 * j * (circleRadius + 1) + circleRadius + 1,
y:top + 2 * i * (circleRadius + 1) + circleRadius + 1,
vx:parseInt(4 + Math.random()*4) * Math.pow(-1,parseInt(Math.random()*1000)),
vy:-4,
g:3 + parseInt(Math.random()*4-2),
color:color[parseInt(Math.random() * color.length)]
}
balls.push(aBall);
}
}
}
}

function getCurrShowTimeSeconds(){
var currTime = new Date();
//    此段为倒计时功能
//    var period = (endTime.getTime() - currTime.getTime())/1000;
//    period = Math.round(period);
//    return period > 0 ? period : 0;
var result = currTime.getHours()*3600 + currTime.getMinutes()*60 +currTime.getSeconds();
return result;
}

function drawTime(ctx){
ctx.clearRect(0,0,WINDOW_WIDTH,WINDOW_HEIGHT);  //刷新画布区域

var hour = parseInt(currShowTimeSeconds / 3600);
var minuter = parseInt((currShowTimeSeconds - hour * 3600)/60);
var second = parseInt(currShowTimeSeconds % 60);
drawDigit(parseInt(hour / 10), BASE_LEFT, BASE_TOP, ctx);
drawDigit(parseInt(hour % 10), BASE_LEFT + 15 * (circleRadius + 1), BASE_TOP, ctx);
drawDigit(10, BASE_LEFT + 30 * (circleRadius + 1), BASE_TOP, ctx);
drawDigit(parseInt(minuter / 10), BASE_LEFT + 39 * (circleRadius + 1), BASE_TOP, ctx);
drawDigit(parseInt(minuter % 10), BASE_LEFT + 54 * (circleRadius + 1), BASE_TOP, ctx);
drawDigit(10, BASE_LEFT + 69 * (circleRadius + 1), BASE_TOP, ctx);
drawDigit(parseInt(second / 10), BASE_LEFT + 78 * (circleRadius + 1), BASE_TOP, ctx);
drawDigit(parseInt(second % 10), BASE_LEFT + 93 * (circleRadius + 1), BASE_TOP, ctx);

for(var i = 0; i < balls.length; i++){
ctx.beginPath();
ctx.fillStyle = balls[i].color;
ctx.arc(balls[i].x,balls[i].y,circleRadius,0,2 * Math.PI,true);
ctx.fill();
}
}

function drawDigit(num,x,y,ctx){
var digitMatrix = digit[num];
ctx.fillStyle = "#5F4EE9";
for(var i = 0; i < digitMatrix.length; i++){
for(var j = 0; j<digitMatrix[i].length; j++){
if(digitMatrix[i][j]){
ctx.beginPath();
var cx = x + 2 * j * (circleRadius + 1) + circleRadius + 1;
var cy = y + 2 * i * (circleRadius + 1) + circleRadius + 1;
ctx.arc(cx, cy, circleRadius, 0, 2 * Math.PI, true,ctx);
ctx.closePath();
ctx.fill();
}
}
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  html 动画 工作