项目演示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
。
它的做用是控制屏幕中效果的个数。
作这个动画的最大收获是: