惟美的櫻花飛舞動畫

背景

這件事要從大促提及....每一年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以後纔會開始執行繪製,那咱們來看看效果。

那能夠看到,和以前的動畫比起來,確實多了一個漸入的效果,固然時間能夠由你們進行優化。

花瓣須要依次飛出屏幕

仔細分析一下,其實這是兩個動做:

  1. 依此
  2. 飛出屏幕

依次

目前來看依此,其實咱們是已經實現了,咱們每個花瓣都執行的是同一套動做流程,可是由於以前的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。

我總結了一下個人拆解:

  • 對花瓣進行屬性的抽象,就是找共同點
  • 隨機分佈櫻花以及優化
  • 花瓣入場的縮放過程
  • 花瓣入場的透明度變換過程
  • 花瓣入場的漸序透視
  • 花瓣落地以後飛出屏幕,以及優化

已上,就是我本次的分享,不知道有沒有收穫呢?要有收穫的話,不妨點個贊吧,嘿嘿嘿🌹。同時也歡迎你們提出各類意見與思路。

相關文章
相關標籤/搜索