這件事要從大促提及....每一年411都是豌豆公主的專屬櫻花節。既然是櫻花節,那麼不少活動和元素都是圍繞着櫻花展來的。就好比此次要分享的內容,須要作一個櫻花飛舞的畫面,固然因爲時間問題,可能還不是那麼炫酷,這裏主要是和你們分享一些思路,具體的效果,你們能夠本身發揮想象不是。canvas
這裏我們暫且只說一部分需求的描述。整個活動就是一個抽獎活動,點擊抽獎按鈕以後,須要有一個櫻花飛舞的轉場動畫。動畫要求:每一個花瓣都能從大到小的的變化,而且可以作出層次感的透視效果,當所有櫻花都縮小完成,開始逐漸飛出屏幕。markdown
動畫的動做主要分幾個步奏:dom
- 花瓣要能從大到小的變換,咱們能夠經過scale來處理。
- 花瓣從大到小的變化須要有層次感,也就是說每一個花瓣都須要有延遲繪製的能力,也就是須要delay方法
- 花瓣依此飛出屏幕。這個的話其實能夠沿用上邊delay的邏輯,只要一片花瓣延遲了縮小動做,那麼理論上以後的動畫都會慢半拍。
每一個花瓣都應該擁有本身的屬性
這句話應該怎麼去理解? 你們不妨這麼想,咱們的這一組櫻花飛舞的動畫其實不是一個花瓣就能完成的,可是若是針對所有花瓣一個個的處理就太麻煩了,因此咱們就須要一個類將花瓣進行抽象,那這個類須要那些東西呢?以下:函數
- 花瓣的圖片信息:imgResource
- 花瓣的座標:x、y
- 花瓣的大小:width、height
- 花瓣的縮放程度:scale
- 最終縮放的倍數:finalSizeScale
- 花瓣的旋轉度數:rotate
- 花瓣的透明度:opacity
- 花瓣的變化的係數(包括透明度,大小,移動速度):speed
- 改變花瓣大小與透明度以前的等待時間:delay
- 花瓣的移動前的等待時間:shrinkDelay
代碼以下優化
// 生成櫻花;
for(let i = 0; i < 20; i++) {
sakuraList[i] = [];
for (let j = 0; j < i; j++) {
sakuraList[i].push(new Sakura(
ctx,
imgInfo,
Math.random() * canvasCenterX * 4 - canvasCenterX * 2, //x
Math.random() * canvasCenterY * 4 - canvasCenterY * 2, //y
1.8, // scale
(Math.random() * 180) * Math.PI/180, // rotate
// 速度這裏i越小越快
Math.floor(Math.random() * 3 + 3) / 100, // speed
0, //opacity
canvas, //canvas
Math.floor(Math.random() * 7 + 20) / 100, // final
i * 2, //delay
));
}
}
複製代碼
經過上邊的代碼,咱們不難看出這是個初始化櫻花花瓣的過程,其中咱們應該關注的是隨機的位置的計算。動畫
這裏邊有一個比較關鍵的就是,如何能夠相對隨機均勻的分佈花瓣,也許不少同窗第一反應就是用random
,而後經過畫布的width、height
來進行隨機分佈。
這個方法乍一看,感受一切正常,那咱們也用代碼實現一下,看看分佈效果。ui
Math.random() * canvas.width, //x
Math.random() * canvas.height, //y
複製代碼
如圖所示,其實這個分佈效果並不會很理想。解決方案也很簡單就是咱們能夠經過畫布的中心點進行計算,用代碼實現一下~this
Math.random() * canvasCenterX * 4 - canvasCenterX * 2, //x
Math.random() * canvasCenterY * 4 - canvasCenterY * 2, //y
複製代碼
這麼一看,是否是均勻很多了。 spa
需求上說,我們花瓣的出現須要由大到小的一個變化過程,那麼邏輯也就比較簡單了,在不考慮其餘狀況下,咱們只須要定義兩個值:設計
- 花瓣在初始化的時候的大小
- 花瓣花瓣最終靜止以後的大小
這兩個值咱們在初始化的時候就已經定義了,代碼片斷就是:
class Sakura {
/** * imgResource 圖片地址 * x * y * scale 圖片縮放 * rotate 旋轉 * speed 速度 * opacity 透明度 * finalSizeScale * delay 延遲 */
constructor( imgResource, x = 0, y = 0, scale = 4, rotate = 0, speed = 1, opacity = 0, finalSizeScale = 0.2, delay ) {
this.imgResource = imgResource;
this.width = imgResource.width;
this.height = imgResource.height;
this.x = x;
this.y = y;
this.scale = scale; // 初始化的大小
this.rotate = rotate;
this.speed = speed;
this.opacity = opacity;
this.finalSizeScale = finalSizeScale; // 最終要縮放到的大小
this.delay = delay;
this.shrinkDelay = 7;
}
}
複製代碼
那有了這兩個值,咱們就能夠經過刷新機制進行計算,那先看看效果。
/** * 縮放方法 * sakura就是咱們以前初始化的花瓣對象 */
changeScale(sakura) {
if(sakura.scale > sakura.finalSizeScale){
sakura.scale -= sakura.speed;
} else {
sakura.scale = sakura.finalSizeScale;
}
},
複製代碼
那麼動畫效果是實現了,可是總覺的是有些生硬,那咱們能夠加入一些緩動的算子進入,進行一些優化。
/** * 縮放方法 * sakura就是咱們以前初始化的花瓣對象 */
changeScale(sakura) {
if(sakura.scale > sakura.finalSizeScale){
sakura.scale -= ((sakura.scale - sakura.finalSizeScale) * sakura.speed);
} else {
sakura.scale = sakura.finalSizeScale;
}
},
複製代碼
如上代碼,咱們將縮小的方式進行了一些優化,替換上了一個緩動公式
當前值 += (目標值-當前值)*係數
固然,這個係數你們能夠慢慢的去調本身想要的感受,我這裏就是個小丑。
透明度的變化其實與縮放是相同的思路,因此函數也是能夠直接使用
/** * 透明度變換的方法 * sakura就是咱們以前初始化的花瓣對象 */
changeOpacity(sakura) {
if(sakura.opacity < 1) {
// 當前值 += (目標值-當前值)*係數
sakura.opacity += ((1 - sakura.opacity) * sakura.speed);
} else {
sakura.opacity = 1;
}
},
複製代碼
這邊透明度的變換就直接使用以前的緩動公式來實現,讓咱們看看效果
看樣子,貌似沒有問題,只是咱們花瓣太多了,顯得不那麼明顯,可是從和上邊的動圖進行比較,其實已經發生了一些視覺上的變化
須要一個層次感,說白了,就是須要有一個對每個花瓣進行一個延遲設置,以此來實現一個視覺差。那其實咱們能夠經過設定一個delay
屬性,經過減法操做來實現一個delayAnimation
的方法。
/** * 延遲方法 * sakura就是咱們以前初始化的花瓣對象 */
delayAnimation(sakura) {
sakura.delay > 0 && sakura.delay --;
},
複製代碼
delay的方法設計好以後,咱們還須要考慮一點就是對透明度和縮放作一個攔截,要否則delay就沒有任何意義了。
drawSakuraAnimation() {
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
for (let item of sakuraList) {
for(let item2 of item) {
this.ctx.globalAlpha = item2.opacity;
item2.delay <= 0 && this.changeScale(item2);
item2.delay <= 0 && this.changeOpacity(item2);
if(item2.opacity > 0) {
this.ctx.save();
this.ctx.rotate(item2.rotate);
item2.ctx.drawImage(item2.imgResource, item2.x, item2.y, item2.width*item2.scale, item2.height*item2.scale);
this.ctx.restore();
}
this.delayAnimation(item2);
}
}
window.requestAnimationFrame(this.drawSakuraAnimation);
},
複製代碼
那如上,能夠看到只有在delay爲0以後纔會開始執行繪製,那咱們來看看效果。
那能夠看到,和以前的動畫比起來,確實多了一個漸入的效果,固然時間能夠由你們進行優化。
仔細分析一下,其實這是兩個動做:
- 依此
- 飛出屏幕
目前來看依此,其實咱們是已經實現了,咱們每個花瓣都執行的是同一套動做流程,可是由於以前的delayAnimation
方法,就會致使每一個花瓣的狀態是有差別的,可是整體的運動曲線不變。說白了就是,delay致使有些花瓣執行方法慢半拍。
飛出屏幕的話,咱們又能夠用到緩動方法啦~ 可是這裏要注意的是,由於咱們不可能一開始就會飛,是須要等到必定的值以後,花瓣纔會飛舞。所以須要一個值進行一個判斷,這裏我用的是透明度
drawSakuraAnimation() {
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
for (let item of sakuraList) {
for(let item2 of item) {
this.ctx.globalAlpha = item2.opacity;
item2.delay <= 0 && this.changeScale(item2);
item2.delay <= 0 && this.changeOpacity(item2);
if(item2.opacity > 0) {
this.ctx.save();
this.ctx.rotate(item2.rotate);
item2.ctx.drawImage(item2.imgResource, item2.x, item2.y, item2.width*item2.scale, item2.height*item2.scale);
this.ctx.restore();
}
// 當透明度到達一個值以後會進行一些位移操做
// 當前值 += (目標值-當前值)*係數
if (item2.opacity >= 0.9) {
item2.x += (-600-item2.x)*item2.speed;
item2.y += (-600-item2.y)*item2.speed;
item2.ctx.drawImage(item2.imgResource, item2.x, item2.y, item2.width*item2.scale, item2.height*item2.scale);
}
this.delayAnimation(item2);
}
}
window.requestAnimationFrame(this.drawSakuraAnimation);
},
複製代碼
你們不難看出,我這邊是在當透明度大於0.9的時候,花瓣就會往一個方向進行移動。接下來咱們看看效果。
由於gif把動畫的一些關鍵幀給省略了,因此略顯尷尬,可是請相信我,其實還能夠...可是其實我以爲可能飛出屏幕太快了,我想等花瓣落地差很少了再飛舞,那容我偷個懶吧,我用一個簡單的減法操做一下。
細心的同窗可能以前有看到,我聲明的屬性裏邊有一個shrinkDelay
尚未用到,那這個其實就是用來做「依次」的延遲的。來來來~show me the code!
drawSakuraAnimation() {
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
for (let item of sakuraList) {
for(let item2 of item) {
this.ctx.globalAlpha = item2.opacity;
item2.delay <= 0 && this.changeScale(item2);
item2.delay <= 0 && this.changeOpacity(item2);
if(item2.opacity > 0) {
this.ctx.save();
this.ctx.rotate(item2.rotate);
item2.ctx.drawImage(item2.imgResource, item2.x, item2.y, item2.width*item2.scale, item2.height*item2.scale);
this.ctx.restore();
}
if (item2.opacity >= 0.9) {
item2.shrinkDelay -= 0.1;
if (item2.shrinkDelay <= 0) {
item2.x += (-600-item2.x)*item2.speed;
item2.y += (-600-item2.y)*item2.speed;
}
item2.ctx.drawImage(item2.imgResource, item2.x, item2.y, item2.width*item2.scale, item2.height*item2.scale);
}
this.delayAnimation(item2);
}
}
window.requestAnimationFrame(this.drawSakuraAnimation);
},
複製代碼
能夠看到,我就是簡單的用shrinkDelay
的自減,來進行一個延遲,不要和我同樣懶.....那看一下效果
怎麼樣?就目前來看,其實咱們的需求基本上是完成了,固然你們能夠機進行一些優化~碼起來!
通關了這個動畫,其實你們會發現,當遇到一個稍微複雜一些動畫,咱們就能夠進行一個動做拆解,固然不包括那些腦中自帶顯卡的大神們,23333。
我總結了一下個人拆解:
- 對花瓣進行屬性的抽象,就是找共同點
- 隨機分佈櫻花以及優化
- 花瓣入場的縮放過程
- 花瓣入場的透明度變換過程
- 花瓣入場的漸序透視
- 花瓣落地以後飛出屏幕,以及優化
已上,就是我本次的分享,不知道有沒有收穫呢?要有收穫的話,不妨點個贊吧,嘿嘿嘿🌹。同時也歡迎你們提出各類意見與思路。