緊接上一篇文章 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