紧接上一篇文章 Canvas基础-粒子动画Part1 其实这篇早在一个星期之前就应该发了,无奈事情太多,而且我又跑去写微信公众号了。
粒子动起来
有了上一篇的基础,我们已经可以获得粒子,并将轮廓显示在Canvas上,如果看了之前我写的一些关于 Canvas动画啊,画图啊什么文章的话,其实应该已经很清楚如何去让这些粒子动起来。
这里我们重新定义一个draw2()
方法,init()
等还是和Part1一样,对图片进行取样,获取粒子的位置,保存在Dot
对象里面,这里就省略了。
要让粒子动起来无非是不断的计算粒子的位置,如果是线性增加的话,会比较生硬,这里使用了Tween的缓动函数,可以看一下jquery.easing.js里面的缓动函数,直接拿来用就可以了,效果很多,我这里只选择了一个easeInOut的效果。
// t 当前时间
// b 初始值
// c 总位移
// d 总时间
function easeInOutCubic(t, b, c, d) {
if ((t/=d/2) < 1) return c/2*t*t*t + b;
return c/2*((t-=2)*t*t + 2) + b;
}复制代码
有了缓动函数,我们还需要在每个Dot
对象中新增一些信息,
function Dot(centerX, centerY, radius) {
this.x = centerX;
this.y = centerY;
this.radius = radius;
this.frameNum = 0;
this.frameCount = Math.ceil(3000 / 16.66);
this.sx = 400;
this.sy = 400;
}复制代码
x,y,radius都和Part1一样,分别表示,圆心坐标和半径,新增的几个含义如下:
- frameNum, 表示为这个粒子当前在第几帧;
- frameCount, 表示一共有多少帧,一般来说我们不会直接知道做完这个动画一共有多少帧,所以这里我们是算出来的,
parseInt(3000 / 16.66)
中3000表示3000毫秒,也就是整个动画耗时3秒,而16.66是因为按60FPS来算,浏览器对每一帧画面的渲染工作需要1秒 / 60 = 16.66毫秒
,算出来之后再做个向上取整,就算出总帧数。 - sx, 起始点x值,这里为了方便就写死了,也可以用随机数;
- sy, 起始点y值。
然后我们来写draw2
方法:
var rafId = null,
finishCount = 0;
function draw2() {
var imgW = img.width,
imgH = img.height,
sx = winWidth/2-imgW/2,
sy = winHeight/2-imgH/2;
ctx.clearRect(0, 0, winWidth, winHeight);
ctx.fillStyle = "#000";
var len = dotList.length,
curDot = null,
frameNum = 0,
frameCount = 0,
curX, curY;
finishCount = 0;
for(var i=0; i < len; i+=1) {
// 当前粒子
curDot = dotList[i];
// 获取当前的time和持续时间和延时
frameNum = curDot.frameNum;
frameCount = curDot.frameCount;
ctx.save();
ctx.beginPath();
if(frameNum < frameCount) {
curX = easeInOutCubic(frameNum, curDot.sx, curDot.x-curDot.sx, curDot.frameCount);
curY = easeInOutCubic(frameNum, curDot.sy, curDot.y-curDot.sy, curDot.frameCount);
ctx.arc(curX, curY, curDot.radius, 0, 2*Math.PI);
curDot.frameNum += 1;
} else {
ctx.arc(curDot.x, curDot.y, curDot.radius, 0, 2*Math.PI);
finishCount += 1;
}
ctx.fill();
ctx.restore();
if (finishCount >= len) {
cancelAnimationFrame(rafId);
return;
}
}
rafId = requestAnimationFrame(draw2);
}复制代码
代码虽然有点长,但是还是比较好理解的。
- 动画进行中的时候
frameNum < frameCount
,通过前面的缓动函数计算出当前应该到达的x,y值,然后画到Canvas上并将这个点的帧数加一。 - 最后一个帧的时候,也就是
else
条件,就不要画计算出来的值了,画实际应该在的位置。 - 一定要注意:
ctx.beginPath()
和ctx.fill()
,不然你的画布上啥子都没有。 - 定义了一个
finishCount
,用来在每次画粒子的时候统计有多少个是已经跑到相应位置了,所以每次循环开始前都要将其置为0,当跑到位的粒子数量和总粒子数量相等的时候,就调用cancelAnimationFrame
并退出,停掉相应的绘制,不要浪费资源。 - 还有就是判断是否停掉要放在
ctx.fill()
之后做,不然有会出现少了一个粒子的情况。
这样出来的效果:
是不是感觉被骗了,粒子整体移动,一开始一团团的,最后才有点粒子化,粒子感不明显,说好的酷炫狂拽屌炸天呢?
别急,知道我的尿性,不一开始把所有东西都说出来,而要把整个探索过程讲清楚。这里我们不要将全部的粒子一次都放出去,我们慢慢放。
首先在Dot
对象里面添加两个属性delay
和delayCount
。
function Dot(centerX, centerY, radius) {
.
.
.
this.frameCount = Math.ceil(3000 / 16.66);
.
.
this.delay = this.frameCount*Math.random();
this.delayCount = 0;
}复制代码
- delay,这里表示这个粒子要等待多少帧才开始动,这里简单用总帧数和一个随机数相乘。
- delayCount,表示当前粒子以及等待了多少帧。
改完Dot
对象之后,接下来的事情就好办的,在循环开始之前现判断一下是否达到等待帧数即可。
for(var i=0; i < len; i+=1) {
.
.
.
if(curDot.delayCount < curDot.delay){
curDot.delayCount += 1;
continue;
}
ctx.save();
ctx.beginPath();
if(frameNum < frameCount) {
.
.
.复制代码
最后出来的效果:
粒子化动画的大致原理就是这样的啦,随着我们给Dot
对象添加更多的属性,粒子动画的想象空间还是比较大的,比如加些颜色,加些运动轨迹,通过颜色和透明度做3D效果等等,下篇讲讲这个代码的优化重构吧。
源码地址: github.com/bob-chen/ca…
Part 1 地址: gold.xitu.io/post/57cda0…
Part 3 地址: gold.xitu.io/post/57e7a7…
碎碎念
最近总想记录一些所思所想,写写科技与人文,写写生活状态,写写读书感悟,昨天终于动笔,在微信公众号上写,主要是扯淡和感悟,欢迎关注,交流。
微信公众号:程序员的诗和远方
公众号ID : MonkeyCoder-Life
参考
gsgd.co.uk/sandbox/jqu…
www.zybuluo.com/dengzhirong…
developers.google.com/web/fundame…
www.cnblogs.com/axes/p/3500…