說一說基於JavaScript的動畫

需求

藉助JavaScript,實現網頁動畫效果html

代碼

簡易Demo:es/js_animation_test.html前端

Anikyu源代碼倉庫:anikyugit

先說幾句

本人不才,大學期間搞過各類各樣的設計類軟件,包括作動畫的Flash、After Effects、Premiere,作3D的3ds Max、Cinema 4D;終於,在大學的第三年,專業開了網頁設計課(大二時其實我也有水過一個作網頁的校選修),我又不慎接觸了作網頁的Dreamweaver。Adobe全家桶齊了。程序員

要學作網頁,就得學HTML;要把網頁寫好看,就得學CSS;要讓網頁有點生氣,就得學JavaScript;這不就蹚入了前端這潭渾水。github

// 其實本人並不承認本身是程序員,最可能是會寫網頁的設計師數據庫

大學期間,本人學的是數字媒體技術專業,計算機學院裏的專業,C、C++、數據結構、數據庫概論、計算機圖形學等課程都有上過,外加遊戲原畫、Flash、三維設計一類的偏向設計類的課程,而後本身又稍稍接觸了一點點PHP。曾經我想,要不將來作UI/視頻/3D設計師吧;但最終找工做的時候,認真思考了一下,專業課設計類課程彷佛支持不了個人一些野心,外加我比較菜,大學期間全部軟件都接觸很淺;到後來一直不知道3D模型貼圖咋貼,就試着經過學Three.js來學3D。因此就暫時先試着用Dreamweaver這個技能來找工做吧。npm

至於設計,我認爲總有一天我會回來的~segmentfault

論網頁與傳統動畫的聯繫

本人認爲,網頁與傳統動畫同樣,都是可以使人產生愉悅的能夠用於觀賞的產品;但比傳統動畫更強大的是,網頁可以與用戶直接產生交互。瀏覽器

專業動畫設計軟件裏,動畫的實現通常是逐幀動畫或是關鍵幀動畫。數據結構

  • 逐幀動畫

創做者每一幀繪製一次,最終將多幀連續播放,實現動畫。

  • 關鍵幀動畫

創做者在每一個關鍵點繪製一幀,最終由程序在兩個關鍵幀之間進行插值,獲得中間幀。插值能夠是線性插值,也能夠是包含緩動的插值。

對於網頁動畫,在早期,若動畫效果很複雜,甚是精美,通常都直接使用Flash等插件來製做;簡單的動畫則使用JavaScript中的setInterval()來實現,即每隔若干毫秒(保持性能的同時不讓用戶感到卡頓)對某一對象某一數值進行一次增長,請求一次動畫幀,直到該對象該值的量到達預想值,由此實現動畫。jQuery中的animate()對其進行了封裝。同時還有SVG動畫,使用animate標籤來進行實現。隨着CSS3的出現,DOM元素動畫/過渡效果可使用CSS實現。

固然,CSS動畫的使用也有一些侷限性:

  1. 不可以靈活控制動畫播放進度
  2. 僅能夠支持DOM元素動畫

最近在學習Three.js,其核心庫彷佛不包含動畫。其官方示例有使用Tween.js這個動畫庫,但因爲我比較想試着本身實現一個相似的動畫庫(實際上是由於它的用法和個人想法不太相符)。

所以,我本身封裝了一個基於JavaScript實現的動畫庫 —— Anikyu 。

固然本文先並不着重介紹我所封裝的庫。對於其使用方法,請直接看anikyu的倉庫。

網頁的維度

咱們都知道,網頁有兩個維度:寬度和高度。
若是要用到動畫,那就再加個時間。
(好吧,若是非要說WebGL或是CSS 3D等等三維技術,那再加個深度;此處不討論)。

要讓一個對象會動,就必須引入時間這個概念;由於時間點是一個點,其中包含的是對象在在當前時間點的狀態;既然要讓對象會動,就得找下一個時間點,最終連點成線,就產生了一個時間軸,這樣就有了時間這個維度。

如何在兩個時間點之間進行基本的補間?

如今咱們把兩個時間點之間的變化過程當作一個總體,開始時間點動畫進度爲0,結束時間點動畫進度爲1。

計算過程

設開始時間爲64,結束時間爲1024,當前時間爲256,那麼:

總時間差 = 結束時間 - 開始時間 = 1024 - 64 = 960

當前動畫進度 = (當前時間 - 開始時間)/總時間差 = (256 - 64)/960 = 0.2 ;

設開始值爲222,結束值爲666,那麼:

當前時間點(相對於開始值)的增量
= (結束值 - 開始值)*當前動畫進度
= (666 - 222)*0.2
= 88.8

所以在該時間點下:

當前對象的值 = 開始值 + 當前時間的增量 = 222 + 88.8 = 310.8

以後使用定時器執行上方的步驟,每次時間改變,從新計算一次進度和時間點增量,並賦值給目標對象,直到當前動畫進度達到1,此時動畫完成。

加入緩動效果

在上面的代碼中,咱們已經實現了最簡單的補間,即勻速補間。但在不少狀況下,僅有勻速補間是不夠的,例如咱們可能要實現一個動畫運行先快(慢)後慢(快)/中間快(慢)兩頭慢(快)/稍微出去一點再回來/彈跳等諸如此類的小效果。在After Effects、Cinema 4D等設計類軟件時間軸面板上,咱們能夠對緩動函數進行可視化的編輯,從而咱們能夠直觀地看到緩動函數的曲線。

image

與此相似,藉助CSS3所提供的transition-timing-function屬性,咱們也能夠直接在CSS中實現緩動動畫,其屬性值包括:linear、ease、ease-in、ease-out、ease-in-out,外加這五個屬性值所基於的cubic-bezier(n,n,n,n) —— 三次貝塞爾曲線。

CSS3 所提供的緩動函數已經基本知足咱們平常的一些動畫需求,即上文所說起的先快(慢)後慢(快)/中間快(慢)兩頭慢(快)/稍微出去一點再回來;但對於彈跳效果,因爲CSS3 緩動動畫僅支持兩點之間的簡單緩動,而彈跳效果在動畫過程當中沒法使用CSS3 所支持的三次貝塞爾曲線來表達(彈跳動畫包含多個轉折點),所以沒法使用純CSS來實現彈跳。

但在JavaScript中,咱們能夠本身編寫緩動函數。

緩動函數參見 ECharts 示例頁面

In Out InOut
quadraticIn quadraticOut quadraticInOut
cubicIn cubicOut cubicInOut
quarticIn quarticOut quarticInOut
quinticIn quinticOut quinticInOut
sinusoidalIn sinusoidalOut sinusoidalInOut
exponentialIn exponentialOut exponentialInOut
circularIn circularOut circularInOut
elasticIn elasticOut elasticInOut
backIn backOut backInOut
bounceIn bounceOut bounceInOut

函數曲線示例:

image

若是自行百度,能夠發現這些補間函數基本都是大同小異的。它們都接受一個值 當前動畫播放實際進度k,返回一個通過處理的進度值 k2 用以供計算函數算出當前時間點的變化量。

接上一步的計算過程

假設當前動畫狀態以及所給定值和上一步同樣,緩動函數使用的是bounceIn,則:

當前時間點(相對於開始值)的增量
= (結束值 - 開始值) * 緩動函數(當前動畫進度)
= (666 - 222)*bounceOut(0.2)
= 134.31

所以在該時間點下:

當前對象的值 = 開始值 + 當前時間的增量 = 222 + 134.31 = 356.31

加入定時器以實現動畫

正如上文所說起,動畫的存在依賴於時間這個維度。一個時間點表示一個時間點的狀態,那如何獲得動畫播放期間每個時間點的狀態呢?

答案就是定時器。定時器可讓瀏覽器每間隔一段時間來執行一段函數。在早期瀏覽器(IE9或更低版本)中僅可以使用setInterval()來實現不斷地請求動畫,新版本瀏覽器中則引入了專門用於建立動畫的requestAnimationFrame()。

requestAnimationFrame與setInterval區別在於:

  1. requestAnimationFrame無需手動指定更新間隔時間,其更新跟隨屏幕的更新而更新(由屏幕刷新率來肯定),這種更新機制相似CSS3 動畫的更新;setInterval須要手動指定更新間隔時間,其更新由定時器來控制。這使得使用setInterval請求動畫時可能會因來不及繪製而發生丟幀或過分繪製而產生性能問題,而requestAnimationFrame可以更加精準控制動畫的繪製。
  2. 在頁面不可見時,requestAnimationFrame會暫停執行,而setInterval只要不被清除將會一直執行。這致使setInterval的運行將十分耗電。

Anikyu包含了requestAnimationFrame的 Polyfill,以用於支持IE9瀏覽器。

經過定時器不斷執行下列操做:

獲取到當前時間 - 將當前時間和開始時間進行一系列計算獲得當前時間點狀態 - 將狀態賦值給原始對象

便可實現動畫。

代碼

先定義兩個函數

function clamp(value, min, max) {
    return Math.max(min, Math.min(max, value));
}

clamp函數用於鉗制數值範圍。因爲當前動畫進度的值一定是一個[0,1]區間的值,所以咱們必須讓動畫進度限制在該範圍內,不然可能會致使最終效果值小於/大於預期值

function bounceOut(k) {
    if (k < (1 / 2.75)) { return 7.5625 * k * k; }
    else if (k < (2 / 2.75)) { return 7.5625 * (k -= (1.5 / 2.75)) * k + 0.75; }
    else if (k < (2.5 / 2.75)) { return 7.5625 * (k -= (2.25 / 2.75)) * k + 0.9375; }
    else { return 7.5625 * (k -= (2.625 / 2.75)) * k + 0.984375; }
}

bounceOut函數是上文所說起的30個緩動函數之一,可在動畫進度快要結束時產生彈回的效果。將當前動畫進度傳入後,能夠得到一個通過處理的進度,在實際計算時根據該進度獲得當前時間點狀態,實現變速運動。若當前動畫進度不通過該函數處理,則動畫爲勻速運動。

獲取動畫對象,並定義初始值、結束值,獲取動畫起始時間

let el = document.getElementById('el')

let init = 222;
let end = 666;
let timeDelta = 960;

let startTime = Date.now()

編寫動畫函數

function animate() {
    interval = requestAnimationFrame(animate)
    let loop = () => {
        let currentProgress = (Date.now() - startTime) / timeDelta

        currentProgress = clamp(currentProgress, 0, 1)

        let sumNumber = (end - init) * bounceOut(currentProgress)

        // let sumNumber = (end - init) * currentProgress

        let currentStatus = init + sumNumber

        if (currentProgress === 1) {
            cancelAnimationFrame(interval)
            el.style.transform = `translate(${end}px)`
        } else {
            el.style.transform = `translate(${currentStatus}px)`
        }
    }
    loop()
}
animate()

以上就是我對JavaScript動畫的理解。藉此機會,我也封裝了一個上文所說起的動畫庫,Anikyu。

Anikyu是本人春節期間在家,正好也是疫情期間,作這個動畫demo時忽然想封裝的一個庫。正巧藉此機會也學習了一下ES6沒用過的特性、Webpack、Babel,同時也將該庫發佈到了 npm

各位要是以爲靠譜不妨來用一下,支持IE 9+瀏覽器,支持Node.js環境。

我也嘗試將Anikyu代碼結構、開發過程寫一篇新的博客:
嘗試經過封裝一個庫來學習JavaScript(ES6)相關特性以及相關構建工具]

#7

可能有點多,我想起來的時候慢慢補充。

相關文章
相關標籤/搜索