從零開始學前端動畫 —— 簡單的特效登陸

最近突然對canvas動畫感興趣,而後就心血來潮的看了一些文章,事先聲明,部分原創,我只是代碼的搬運工。我先上一下截圖,而後再說下個人想法。css

【個人想法】:其實我想作網上特別多的那種,當輸入密碼的時候用手捂住眼睛那種,可是原諒我戲精本色,我想作成那種當輸入密碼的時候,用木槌敲擊小黃人或者閃電劈暈小黃人的那種特效,最好是配上音效。。。可是實現效果都不太好,馬馬虎虎能用不過不完美就對了,因爲達到了學習目的以及時間緣由,就不繼續往下作了,之後有時間可能會作,或者各位CSS大牛幫我完成,完成優秀的特效後記得把連接甩我一臉~萬分感謝。代碼在下方---html

雪花飄落場景

仍是那句話,我只是代碼的搬運工,首先是雪花飄落的場景。參考文章點擊這裏 —— canvas實現雪花飄落,文章上來就貼了代碼,直接就能用,可是沒有任何註釋,既然是初探canvas和動畫,講道理一點註釋都沒有,真心看不太懂,因此我在這裏把代碼來解析一下。效果以下:前端

重點一 一張合適的背景圖

不吹不黑,我的以爲個人審美仍是挺不錯的,這種下雪的特效須要配一個高對比度的背景圖,不然你背景色就是淺色冬天,而後飄白色雪花,其實感受看不太清楚,因此找了好久,發現這張動漫聖誕背景圖,時間點在晚上,配合燈光飄着雪花,仍是很清晰的~css3

重點二 canvas繪製雪花

  • 定義一些場景參數 首先,須要定義一些場景相關的參數,好比,雪花飄落的速度,位置和方向等。代碼以下:
// 存儲全部的雪花
const snows = [];
// 下落的加速度
const G = 0.015;
// 60是人眼所能見到流暢動畫的最小閾值
const fps = 60; 
// 速度上限,避免速度過快
const SPEED_LIMIT_X = 1;
const SPEED_LIMIT_Y = 1;
複製代碼
  • 定義雪花構造函數
/**
   * 雪花對象
   * @param {x} x 
   * @param {y} y 
   * @param {半徑或長寬} radius 
   */
  function Snow(x, y, radius) {
    this.x = x;
    this.y = y;
    // 若是是圓形就是半徑,不然就是長寬相同的正方形
    this.radius = radius;
    // x方向的移動速度,能夠向左也能夠向右,範圍在[-1, 1]
    this.speed_x = 0;
    // y方向的移動速度,只能向下,最快爲1
    this.speed_y = 0;
    // 雪花自身旋轉的角度
    this.deg = 0;
    // x方向,下落速度參數,飄落效果 > 0向左飄; < 0 向右飄 
    this.ax = Math.random() < 0.5 ? 0.005 : -0.005;
  }
複製代碼
  • 生成雪花
// 繪製新雪花,x位置爲隨機數,y爲頂部0,半徑爲隨機數隨機生成大小不一的雪花
new Snow(Math.random() * W, 0, Math.random() * 15 + 5);
複製代碼
  • 爲雪花添加繪製以及更新位置的方法
// 繪製雪花
Snow.prototype.draw = function () {
// 獲取半徑寬高
const radius = this.radius;
// 保存畫布的當前狀態,由於下面用到了變換座標和旋轉畫布
ctx.save();
/**
 * 下面這兩句變化也挺重要的,由於旋轉是按照畫布原點進行的
 * 所以,若是想讓雪花旋轉明顯,就須要將畫布座標移動到雪花的座標點
 * 若是不加上座標轉換,那麼全部雪花都在左上角也就是座標原點旋轉,x, y也不會變,沒有飄落效果
 */
// 將畫布的座標原點移動到(x, y)的位置,canvas默認是(0, 0)
ctx.translate(this.x, this.y);
// 將畫布順時針旋轉的角度
ctx.rotate(this.deg * Math.PI / 180);
// 繪製雪花圖像,由於畫布座標移動到了(x, y),因此從0,0開始就是(-radius, radius)
ctx.drawImage(snowImage, -radius, -radius, radius * 2 , radius * 2);
// 恢復canvas旋轉、translate等操做的狀態,通常與save配合使用就是恢復到上一個save的狀態
// 若是不恢復上一個狀態的話,話不旋轉角度座標都沒變化,也就不會出現動畫效果,必須恢復
ctx.restore();
}
複製代碼
// 更新雪花位置
Snow.prototype.update = function () {
// 雪花自身旋轉的角度增值
const deltaDeg = Math.random() * 0.6 + 0.2;
// 不斷變化x方向的移動速度
this.speed_x += this.ax;
// x向左或者向右速度過大的時候改變方向
if (this.speed_x >= SPEED_LIMIT_X || this.speed_x <= -SPEED_LIMIT_X) {
  this.ax *= -1;
}
// 雪花下落速度,最高是1
if (this.speed_y < SPEED_LIMIT_Y) {
  // 雪花下落速度不斷增長
  this.speed_y += G;
}
// 角度不斷變化
this.deg += deltaDeg;
// x座標不斷變化
this.x += this.speed_x;
// y座標不斷變化
this.y += this.speed_y;
}
複製代碼
  • 實現飄落降低的動畫效果 實現不斷飄落的動畫效果,這裏運用了瀏覽器專門爲動畫提供的API requestAnimationFrame,經過將循環函數做爲參數傳入,該API會不斷進行重繪造成動畫效果。
/**
* 主循環函數, 生成雪花以及繪製更新雪花位置
*/
function loop() {
// 擦除當前畫布內容,不然原有的雪花不會消失,新繪製的雪花不斷覆蓋看起來會像一條雪花白色實線在降低
ctx.clearRect(0, 0, W, H);
// 兩個雪花之間的時間差,不能生成的太快,要否則就成了鵝毛大雪了^_^
const now = Date.now();
// 距離上一次繪製的時間差
deltaTime = now - lastTime;
// 重置結束時間
lastTime = now;
// 時間控制器,當timer > snowLevelTime的時候,才增長雪花,不然不增長雪花
timer += deltaTime;
/**
 * 不加控制的話雪花會特別多, 150~300之間都合適
 */
if (timer > snowLevelTime) {
  snows.push(
    // 繪製新雪花,x位置爲隨機數,y爲頂部0,半徑爲隨機數隨機生成大小不一的雪花
    new Snow(Math.random() * W, 0, Math.random() * 15 + 5)
  );
  timer %= snowLevelTime;
}
const length = snows.length;
snows.map(function (s, i) {
  s.update();
  s.draw();
  if (s.y >= H) {
    snows.splice(i, 1);
  }
});
// 避免失真,瀏覽器頁面每次重繪以前調用
requestAnimationFrame(loop);
}
複製代碼

小黃人效果

我只是代碼的搬運工,這裏有另外一個大神寫了一篇很是詳細的文章,CSS3手繪小黃人,我只是將代碼進行了適配的擴展,讓代碼在大部分屏幕分辨率下均可以使用。這裏就不寫過程了,文章裏寫的真的挺清楚的~git

眩暈效果

眩暈效果其實我也想找現成的代碼,我喜歡站在巨人的肩膀上,可是奈何找不到,連圖片都找不到,最後沒辦法了,本身寫吧,就簡單的實現了下眩暈效果,至於眩暈的旋轉動畫,下面會簡單介紹:github

/* CSS */
 .circle-container {
      position: relative;
      width: 45px;
      height: 45px;
      transform: rotate(180deg);
    }
    .one-circle {
      position: absolute;
      width: 45px;
      height: 45px;
      background-color: #fff;
      border-left: 3px solid #333;
      border-top: 3px solid #333; 
      border-radius: 50%; 
    }
    .two-circle {
      position: absolute;
      top: 9px;
      width: 34px;
      height: 34px;
      background-color: #fff;
      border-right: 3px solid #333;
      border-bottom: 3px solid #333; 
      border-radius: 50%; 
    }
    .three-circle {
      position: absolute;
      bottom: 10px;
      left: 8px;
      width: 23px;
      height: 23px;
      background-color: #fff;
      border-top: 3px solid #333;
      border-left: 3px solid #333; 
      border-radius: 50%; 
    }
    .four-circle {
      position: absolute;
      bottom: 0px;
      right: 6px;
      width: 13px;
      height: 13px;
      background-color: #fff;
      border-bottom: 3px solid #333;
      border-right: 3px solid #333; 
      border-radius: 50%; 
    }
  /* Html */
  <div class='circle-container'>
    <div class="one-circle">
      <div class="two-circle">
        <div class="three-circle">
          <div class="four-circle"></div>
        </div>
      </div>
    </div>
  </div>
複製代碼

不管是代碼仍是實現都挺簡單的,可是奈何是原創,仍是寫出來吧,O(∩_∩)O哈哈~web

隔行如隔山啊,才發現即便是前端,也有如此多的分支,確實像張鑫旭老師自我介紹那樣,前段偏前工程師,我卻是以爲偏前偏後偏業務每一個方向都值得深刻研究啊,不過我這個階段仍是大雜燴一下吧。既然是對動畫感興趣,用完canvas了,也就想了解了解其餘幾種實現方法了,順便簡單學習一下,下面是學體會。canvas

聊聊幾種瀏覽器動畫實現方法:

setTimeout和setInterval

最原始的方法就是使用window.setTimout()或者window.setInterval()經過不斷更新元素的狀態位置等來實現動畫,前提是畫面的更新頻率要達到每秒60次才能讓肉眼看到流暢的動畫效果。瀏覽器

這個就不貼代碼了,說實話,應該沒有人在用這種方式實現動畫了,若是有,爲你的網頁性能所擔心~bash

CSS3的transition和transform屬性

transtion能夠理解爲過渡,兩點之間,連續性的變化。transform理解爲變換,平移、旋轉等是經常使用的,transform + transition結合起來,也就是 過渡 + 變換就會造成簡單的動畫。 transtion實現動畫的規則是須要定義對應的屬性,好比我想讓寬度發生變化,那麼就定義屬性爲width,變化持續時間,也就是多久完成變換,最後在指定事件中改變定義的屬性值便可。

// 實現鼠標放到籃球上籃球放大
.ball {
width: 128px;
height: 128px;
transition-property: width,height;
transition-duration: 2s;
-moz-transition-property: width,height; /* Firefox 4 */
-moz-transition-duration: 2s; /* Firefox 4 */
-webkit-transition-property: width,height; /* Safari and Chrome */
-webkit-transition-duration: 2s; /* Safari and Chrome */
-o-transition-property: width,height; /* Opera */
-o-transition-duration: 2s; /* Opera */
}
.ball:hover {
width: 256px;
height: 256px;
}
複製代碼

CSS3的animation和keyframes

與transition和transform不同,animation就能夠直接理解爲動畫,我的以爲功能也要更強大一些。 如上面說的那樣,animation就是CSS爲動畫而出的,基本能夠完成大部分簡易的動畫效果,他須要定義一個動畫特效名,而後經過keyframe定義特效效果。下面我簡單實現了一個籃球自由落體的效果,很粗糙,只是一個簡單的demo:

.ball-animation {
  position: absolute;
  width: 128px;
  height: 128px;
  transform: rotate(0);
  animation: ball-drop 6s linear;
  animation-fill-mode: forwards;
  -webkit-animation: ball-drop 6s linear;
  -webkit-transform: rotate(0);
  -webkit-animation-fill-mode: forwards;
}
@keyframes ball-drop {
  0% {
    transform: rotate(0);
    top: 0
  }
  10% {
    transform: rotate(60);
    top: calc(var(--height) * 1px );
  }
  20% {
    transform: rotate(120);
    top: 40px;
  }
  30% {
    transform: rotate(240);
    top: calc(var(--height) * 1px );
  }
  40% {
    transform: rotate(300);
    top: 80px;
  }
  50% {
    transform: rotate(360);
    top: calc(var(--height) * 1px );
  }
  60% {
    transform: rotate(60);
    top: 160px;
  }
  70% {
    transform: rotate(120);
    top: calc(var(--height) * 1px );
  }
  80% {
    transform: rotate(180);
    top: 380px;
  }
  90% {
    transform: rotate(240);
    top: calc((var(--height) - 20) * 1px );
  }
  100% {
    transform: rotate(260);
    top: calc(var(--height) * 1px );
  }
}
複製代碼

雖然實現的很粗糙,可是我仍是寫了影子之類的東西,因此也算是走心,哈哈。詳細的介紹我就很少說了,我以爲去看張鑫旭老師的文章就能夠了,淺顯易懂,傳送門

requestAnimationFrame

由於使用canvas實現動畫最重要的就是這個API了,因此放到最後來講。MDN裏關於它有這麼個高亮注意:若您想要在下次重繪時產生另外一個動畫畫面,您的回調例程必須調用 requestAnimationFrame()。因此也就是說,若是想要不斷地重繪產生動畫效果必須調用這個API。

window.requestAnimationFrame() 方法告訴瀏覽器您但願執行動畫並請求瀏覽器在下一次重繪以前調用指定的函數來更新動畫。該方法使用一個回調函數做爲參數,這個回調函數會在瀏覽器重繪以前調用。 通常來講,人眼若是想看到流暢的動畫,至少每秒更新60次,固然你也能夠控制想要刷新的次數,同時,requestAnimationFrame()會返回一個整形ID,經過這個ID你能夠調用cancleAnimationFrame(ID)來中止動畫經過下面的代碼:

let timeoutId;
let animateId;
const fps = 20; // 好比我每秒就想刷新20次
window.requestAnimationFrame = (function () {
    return window.requestAnimationFrame ||
      window.webkitRequestAnimationFrame ||
      window.mozRequestAnimationFrame ||
      window.oRequestAnimationFrame ||
      window.msRequestAnimationFrame ||
      function (callback) {
        timeoutId = setTimeout(callback, 1000 / fps);
      }
  })();
}
function loop() {
  // 執行動畫代碼
  animateId = requestAnimaitonFrame(loop);
}

// 中止動畫,先中止timeout再中止requestAnimationFrame
clearTimeout(timeoutId);
cancleAnimationFrame(animateId);
複製代碼

一樣是js實現動畫效果,因爲這個API是專門用來在瀏覽器實現動畫的,因此瀏覽器對其也進行了優化,相比setTimeout和setInterval的形式來講,requestAnimationFrame()性能更好,在大多數瀏覽器裏,當運行在後臺標籤頁或者隱藏的 裏時,requestAnimationFrame() 會暫停調用以提高性能和電池壽命。

animation和requestAnimationFrame的對比

其實不用比也知道,同一個動畫效果,用純CSS3實現和用requestAnimationFrame這種js實現手段,確定CSS3的性能要高一些的。下面是我作的同一個動效,兩者渲染時間對比: animation:

requestAnimationFrame

好吧,我實際上是個假測試,我以爲首先我沒有控制速度一致性,其次,我在annimation實現的時候使用了calc函數計算位置,可能對性能也會有影響,不過在重繪時間上,animation的方法仍是要優秀一點的。

強烈聲明:我只是爲告終果而做比較,沒有誰高誰低的意思,我以爲根據場景來使用吧~

能夠改進的地方

最後實現的代碼有不少能夠改進的地方,在這裏記錄下來,萬一哪位大神心情好給我改了咋辦,O(∩_∩)O哈哈~。

  • 木槌可使用CSS3繪製,而後敲擊小黃人腦殼出現撞擊聲和眩暈聲
  • 雪花飄落過程遇到遮擋物,小黃人和輸入框的時候,應該停留下來,這樣更真實
  • 雪花落到地面應該造成積雪,可是積雪太多影響性能就沒加~加也很容易,判斷雪花位置指定雪花不重繪就能夠了。
  • 原諒我沒有作移動端適配,下面的demo儘可能別用手機來打開了。。。pad可能還能夠,O(∩_∩)O哈哈~

總結

這兩天簡單看了看動畫,感受一入前端深似海啊,目前計劃仍是全部感興趣的都涉獵一下,而後慢慢選擇一個方向吧~代碼地址:luffyZhou的動畫Demo歡迎你們多提意見,多給STAR

只clone不star,就不夠意思了哦,我會在內心詛咒你的^_^

相關文章
相關標籤/搜索