canvas系列三之煙花效果實現
寫在最前:
唉,時間過得真是快啊,距離上次寫demo已經是半個月前了,原來想得是每週寫一篇的,但是前段時間工作微忙,所以就沒有demo產出(嘿嘿,偷偷的給自己找了個藉口,還不知道以後能不能堅持產出呢......)。這次寫的還是canvas的東西,利用canvas寫了一個燃放煙花的demo。感覺差不多再寫一兩個demo我就會對這個東西做一下最後的總結吧,畢竟老寫demo也不是個事兒,總得總結下canvas這個東東。好了,廢話不多說,我們直入主題
對了,大佬們記得給我的部落格點贊,要星星哦~
成果展示
Git地址:
實現思路:
照例,我們還是用一種丟擲問題的方式給出該次煙花demo的實現過程,小夥伴們在閱讀的時候也可以通過這些問題自己想象可以怎麼實現。
- 煙花的產生與消失,主要就是怎麼來實現那種流星劃過般的拖尾效果?
- 煙花消失後,如何產生爆炸後四散的效果?
- 四散的花火如何產生那種受"重力"影響而降落的效果以及花火的消失?
基本上,你做到了上述的三步,你的煙花效果就算是做出來了,哈哈,很簡單吧。下面,我們逐個來解決。
詳細說明:
煙花的產生與消失:
看過我之前canvas系列之一的夥伴們,或者對canvas動畫有了解的同學肯定知道在canvas裡面繪製的一個元素它是如何動起來的吧。我們是不是不斷利用clearReact函式去幫助我們清除前一幀的動畫。其實,我們如果不完全清除前一幀的動畫,而是用一個比較透明的顏色去“蓋住”呢?哈哈哈,是不是很簡單,其實就一句程式碼:
ctx.fillStyle = 'rgba(0,0,0,0.05)';複製程式碼
你想拖的尾比較長,你就把透明度改得小一些,如果你想拖得尾短一些,你就把透明度改得大一點。接下來說一說,煙花的消失。對於每個煙花,我們將其宣告為一個物件,裡面包括煙花的大小,煙花的位置,以及煙花的繪製於移動函式。其中,還有一個變數disappear是用來表示煙花的存在於消失的,初始值是false,表示煙花還“活著”。我們可以設定一些邊界條件,本次demo採用的是距離邊界,即當煙花移動到我設定的範圍以外,就將變數disappear設定為true,表示煙花已跪。因為是要不斷的產生煙花,所以我用了一個陣列來儲存已有的煙花數。新的煙花不斷被push進陣列,消失的煙花就不斷從陣列中移除。貼下我的程式碼:
if (fireArr.length) {
fireArr.forEach(function(item, index) {
var marginWidthLeft = parseInt(getRandom(0, canvas.width/5), 10);
var marginWidthRight = parseInt(getRandom(1500, canvas.width), 10);
var marginHeight = parseInt(getRandom(0, 300), 10);
if (item.x >= marginWidthRight || item.x <= marginWidthLeft || item.y <= marginHeight) {
item.disappear = true;
}
if (!item.disappear) {
item.draw();
item.move();
} else {
var removeFire = fireArr.splice(index, 1);
fragments.push(removeFire);
if (fragments.length) {
fragments.forEach(function(item, index) {
if (item[0].boomJudge) {
item[0].boom();
item[0].boomJudge = false;
}
})
}
fireArr.push(createRandomFire(CreateFireObj));
}
})
}複製程式碼
這裡我設定了左右和上邊界,都取得是一定範圍內的隨機值。當disappear值變為true的時候就從陣列中移除相應的元素,之後再新增新的元素,保證頁面內是有固定數量的煙花。
煙花消失,爆炸效果的產生:
這是本次demo的難點,也是重點。就是怎麼剛好在煙花消失的時候同時產生許許多多的小煙花呢?小煙花又是如何炸裂開來的呢?剛開始我也是一頭霧水的,後來想著想著就慢慢的有些思路了。
對於每個煙花,我給其增加了一個爆炸函式boom,這個boom函式的作用就是在煙花消失也即爆炸的時候,瞬間產生隨機數量的小花火(其實這個數量我們可以隨機控制在一個範圍之內)。對於每個小花火,其顏色隨機,而且在它生的時候我為其生成了一個它所能到達的目標位置。這個目標位置是以煙花爆炸的位置為圓心,半徑隨機,利用圓的方程,給一個隨機的角度,這樣小火花產生後就可以在以煙花爆炸位置為圓心,隨機半徑的圓弧線的隨機位置出現。為了便於管理,每個煙花都有一個陣列變數fragArr,用於儲存這些新產生的小花火。每個小花火也都是一個物件,同樣,與煙花類似,小花火物件裡面儲存了花火的大小、位置、移動函式、繪製函式以及運動終止位置等資訊。相關程式碼如下:
//煙花爆炸,產生碎片
this.boom = function() {
var scope = Math.round(getRandom(10, 40));
for (var i=0; i<scope; i++) {
var angel = getRandom(0, 2*Math.PI);
var range = Math.round(getRandom(50, 300));
var targetX = this.x + range*Math.cos(angel);
var targetY = this.y + range*Math.sin(angel);
var r = Math.round(getRandom(120, 255));
var g = Math.round(getRandom(120, 255));
var b = Math.round(getRandom(120, 255));
var color = 'rgb(' + r + ',' + g + ',' + b + ')';
var frag = new CreateFrag(this.x, this.y, color, targetX, targetY);
this.fragArr.push(frag);
}
}複製程式碼
四散煙花的降落及其消失:
接著上面,在爆炸瞬間我們利用boom函式產生了數量隨機的小花火,每個小花火利用函式CreateFrag生成。我們重點來看下這個函式裡面的關於小火花的移動函式,因為是它產生了小火花如“重力”般下降的效果,程式碼如下:
function CreateFrag(x, y, color, tx, ty) {
var that = this
that.x = x;
that.y = y;
that.ty = ty;
that.tx = tx;
that.color = color;
that.disappear = false;
that.draw = function() {
ctx.save();
ctx.beginPath();
ctx.fillStyle = that.color;
ctx.fillRect(that.x, that.y, 2, 2);
ctx.restore();
}
that.move = function() {
that.ty = that.ty + 0.5;
var dx = that.tx - that.x, dy = that.ty - that.y;
that.x = Math.abs(dx) < 0.1 ? that.tx : (that.x + dx*0.01);
that.y = Math.abs(dy) < 0.1 ? that.ty : (that.y + dy*0.01);
if (dx == 0 || dy == 0 || that.y >= 700 || that.x <= 300 || that.x >= 1700) {
that.fragDisappear = true;
}
}
}複製程式碼
函式接受了五個引數,後面兩個tx、ty,代表了其目標點的x、y座標,我們給出了一個他的當前位置和目標位置的差值,dx和dy,在這個差值的範圍未超出某個給定的閾值(我們這裡給得是0.1)時,小火花的橫縱座標都增加dx和dy的0.01倍,這個倍數越小,越能表現出煙花“爆炸”的細節。這個我們給到一個比較合適的值。小花火的“重力下降”其實得益於一句簡單的程式碼,即:that.ty = that.ty + 0.5。如果沒有這句程式碼,小花火的移動路線是直線,加了這句之後,相當於其垂直速度的增加速度是不斷增大的,這就相當於給了它一個垂直方向的加速度,讓其看起來像受“重力”牽引而不斷下降一樣。對於,花火的消失,其實和煙花的消失原理差不多,都是花火達到某個邊界時讓其消失掉,但是不同的是當煙花的一組花火中的某一個達到邊界條件的時候,我就將該朵花火所在的整個陣列幹掉,而不是一個一個花火單獨去除,貼下程式碼:
var removeFire = fireArr.splice(index, 1);
fragments.push(removeFire);
if (fragments.length) {
fragments.forEach(function(item, index) {
if (item[0].boomJudge) {
item[0].boom();
item[0].boomJudge = false;
}
})
}
if (fragments.length) {
fragments.forEach(function(item1, index1) {
item1[0].fragArr.forEach(function(item2, index2) {
if (item2.fragDisappear) {
fragments.splice(index1, 1);
}
item2.draw();
item2.move();
})
})
}複製程式碼
fragments陣列裡面儲存的是被移出的煙花,這裡我們要注意一點,在fragments.forEach裡面用的是item1[0],而不是item1,是因為在執行廢棄煙花的儲存操作的時候,即var removeFire = fireArr.splice(index, 1);
fragments.push(removeFire); fragments push的是fireArr splice的返回值,是一個含有單個元素的陣列。
經過這樣簡單的三部曲之後,我們的煙花就可以絢麗多姿的在空中爆炸了,怎麼樣,現在回頭看看是不是覺得很簡單呢。
寫在最後:
上述只是實現了一個簡單的煙花產生到爆炸的效果,其實還有很多地方是可以改進的。你比如說,在煙花上升的過程中,我們可以不通過限制它只能在某個範圍內運動,而是通過“重力”的影響,使其垂直速度減為0的時候,產生爆炸效果。還有我們可以新增一段音訊,模擬煙花爆炸的聲音,等等。這些都是值得改進的地方。最後,謝謝大佬們百忙之中閱讀我的這片分享,請一定要給我的部落格點贊哦,你的點贊會成為我前進的不竭動力~