大話 JavaScript 動畫

背景

138.2億年前,世界上沒有時間和空間,或許世界都不存在,在一個似有似無的點上,聚集了全部的物質,它孕育着無限的能量與可能性。javascript

宇宙大爆炸

巨大的內力已沒法被抑制,瞬間爆發,它爆炸了!世界上有了時間和空間,隨着歲月的變遷,時光的流逝,無數的星系、恆星、衛星、彗星造成。咱們生活的地球,只是茫茫宇宙中的一個小小的天體,或許在遙遠的宇宙的另外一邊,會有平行世界的存在,或許在那裏,咱們多是醫生、老師、公務員。科學家說咱們的宇宙正在加速度的膨脹,暗能量在無限吞噬着暗物質,將來的世界將會變得虛無縹緲。css

人類起源

宇宙的造成,帶來了無限可能性,人類釋放着慾望和剋制,對宇宙的渴望產生於公元前五世紀,古巴比倫人經過觀察天體的位置以及外觀變化,來預測人世間的各類事物。在遙遠的古羅馬,人們也舞弄着靈魂,把不羈的想象賦予肉體。Anim,來源於拉丁語,表明着靈魂與生命,表明着全部與生俱來。彷佛世間萬物都存在聯繫,宇宙、天然都存在靈魂。html

動畫的造成

兩萬五前年錢的石器時代,石洞中的野獸奔跑分析圖,這是人類開始試圖捕捉動做最先證據。文藝復興時期的達芬奇畫做上,用兩隻手臂兩條腿來標識上下襬動的動做,在一張畫做上作出不一樣時間的兩個動做。
直到1906年,世界上第一部動畫片《滑稽臉的幽默相》問世。前端

因此動畫是否就是將多個畫面連起來播放呢?java

時間是連續的嗎?是能夠無線分割的嗎?我也不太清楚,你看到的流星、人們的動做是連續的嗎?或許是吧,畢竟現實生活中尚未像瞬間移動這種事情發生吧。git

神經可能不是連續的,生物課學過,神經的傳遞是一個電信號傳遞過程,而且是顆粒的(神經信號),那麼咱們看到的東西在咱們腦海裏的成像必定不是連續的。github

那麼咱們爲何能看到連續的動做呢?瀏覽器

視覺暫留(Persistence of vision),讓咱們看到了連續的畫面,視神經反應速度大約爲1/16秒,每一個人不太同樣,有些人高一點,一些人低一點。上一次視神經傳遞的圖像將會在大腦中存留,直到下一次神經信號到達。維基百科上說,平常用的日光燈每秒鐘大約會熄滅100次,可是你並無感受。app

通常電影的在幀率在24FPS以上,通常30FPS以上大腦會認爲是連貫的,咱們玩的遊戲通常在30FPS,高幀率是60FPS。ide

小時候必定看過翻頁動畫吧,能夠看一看翻頁動畫-地球進化史

前端動畫實現

Atwood 定律:Any application that can be written in JavaScript will eventually be written in JavaScript.

前端作動畫不是什麼新鮮事了,從jQuery時代,到當下,無不是前端動畫橫行的時代。

咱們知道多張不一樣的圖像連在一塊兒就變成了動態的圖像。

在前端的世界裏,瀏覽器在視覺暫留時間內,接二連三的逐幀輸出圖像。每一幀輸出一張圖像。

說起動畫必定會討論到幀率(FPS, Frame Per Second),表明每秒輸出幀數,也就是瀏覽器每秒展現出多少張靜態的圖像。

DOM動畫中的 CSS3

CSS3 動畫是當今盛行的 Web 端製做動畫的方式之一,對於移動設備來講覆蓋率已經很是普遍,在平常開發中可使用。CSS3 動畫只能經過對 CSS 樣式的改變控制 DOM 進行動畫

DOM動畫中的 WebAnimation

WebAnimation 還在草案階段,在Chrome能夠嘗試使用一下。移動設備仍是至關慘烈,iOS 並無開始支持。

Element Animation MDN

Can I Use

CSS3 和 WebAnimation 都只能做用於DOM,那麼,若是咱們想讓 Canvas 上的對象產生動畫,那咱們該怎麼辦呢?

JavaScript

既然咱們知道動畫的原理,其實就是讓用戶看到連續的圖片,而且每一張圖片是有變化的。

對於事物來說,咱們能夠經過改變某些數值來修改他的屬性,歷來改變他的外在展現。好比正方形的邊長,顏色的RGB值,颱風的位置(世界座標),在每一幀去改變這些數值,根據這些數值將對象繪依次制到屏幕上,將會產生動畫。

經過上面的描述,咱們知道,實現一個動畫,實際上是數值隨時間變化,以幀爲時間單位。

在好久好久之前,JavaScript 使用 setInterval 進行定時調用函數。因此可使用setInterval來進行數值的改變。

爲了更好的讓各位前端小哥哥小姐姐們作動畫,出現了requestAnimationFramerequestAnimationFrame 接收一個函數,這個函數將在下一幀渲染以前執行,也就是說,不須要太屢次的計算,只要在下一幀渲染以前,咱們將須要修改的數值修改掉便可。requestAnimationFrame 的幀率和硬件以及瀏覽器有關,通常是60FPS(16.66666666ms/幀)。

咱們利用 Dom 進行動畫的演示~

元素移動

建立一個方塊

<div class=「box」></div>

設置寬高和背景顏色

.box {
    width: 100px;
    height: 100px;
    background: red;
}
const box = document.querySelector('.box') // 獲取方塊元素
let value = 0 // 設置初始值
// 建立每一幀渲染以前要執行的方法
const add = () => {
    requestAnimationFrame(add) // 下一幀渲染以前繼續執行 add 方法
    value += 5 // 每幀加數值增長5
    box.style.transform = `translateX(${value}px)` // 將數值設置給 方塊 的 css 屬性 transform 屬性能夠控制元素在水平方向上的位移
}
requestAnimationFrame(add) // 下一幀渲染以前執行 add 方法

這樣,方塊每幀向右移動 5 像素,每秒移動60*5=300像素,不是每秒跳動一下,而是一秒在300像素內均勻移動哦。

補間動畫

上一個demo實現了小方塊從左到右的移動,可是貌似他會永無止境的移動下去,直到數值溢出,小時候學過flash的朋友都知道補間動畫,其實就是讓小方塊0px到300px平滑移動。其實就是固定的時間點,有固定的位置。

因此咱們只須要根據運動的已過期間的百分比去計算數值。

保持以前的 HTML 和 CSS 不變

/**
 *  執行補間動畫方法
 *
 * @param      {Number}    start     開始數值
 * @param      {Number}    end       結束數值
 * @param      {Number}    time      補間時間
 * @param      {Function}  callback  每幀的回調函數
 */
function animate(start, end, time, callback) {
    let startTime = performance.now() // 設置開始的時間戳
    let differ = end - start // 拿到數值差值
    // 建立每幀以前要執行的函數
    function loop() {
        raf = requestAnimationFrame(loop) // 下一陣調用每幀以前要執行的函數
        const passTime = performance.now() - startTime // 獲取當前時間和開始時間差
        let per = passTime / time // 計算當前已過百分比
        if (per >= 1) { // 判讀若是已經執行
              per = 1 // 設置爲最後的狀態
              cancelAnimationFrame(raf) // 停掉動畫
        }
        const pass = differ * per // 經過已過期間百分比*開始結束數值差得出當前的數值
        callback(pass) // 調用回調函數,把數值傳遞進去
    }
    let raf = requestAnimationFrame(loop) // 下一陣調用每幀以前要執行的函數
}

咱們調用一下補間動畫,讓數值通過1秒勻速從0變成400。

let box = document.querySelector()
animate(0, 400, 1000, value => {
    box.style.transform = `translateX(${value}px)` // 將數值設置給 方塊 的 css 屬性 transform 屬性能夠控制元素在水平方向上的位移
})

一個簡單的勻速補間動畫就這麼被咱們作好了。

非勻速動畫

那萬一,這個動畫不是非勻速的,好比抖一抖啊,彈一彈,那該怎麼辦呢?

固然也是同樣,根據已過期間的百分比去計算數值

時間是勻速的,可是數值不是,若是數值變化是有規律的,那麼咱們就可使用時間來表示數值,建立一個接收時間比例(當前時間百分比),返回當前位置比例(當前位置百分比)的方法。

咱們稱這個方法叫作緩動方法。

若是速度從慢到快,咱們能夠把時間和數值的圖像模擬成如下的樣子。

公式爲 rate = time ^ 2

對應的函數應該是

function  easeIn(time) { // 接收一個當前的時間佔總時間的百分比比
    return time ** 2
}

這個實現加速後抖動結束的效果,在Time小於0.6時是一個公式,time大於0.6是另一個公式。

Time < 0.6 時: Rate = Time / 0.6 ^ 2

Time > 0.6 時: Rate = Math.sin((Time-0.6) ((3 Math.PI) / 0.4)) * 0.2 + 1

最終實現的函數是

function shake(time) {
    if (time < 0.6) {
        return (time / 0.6) ** 2
    } else {
        return Math.sin((time-0.6) * ((3 * Math.PI) / 0.4)) * 0.2 + 1
    }
}

咱們改造一下以前的 animate 函數,接收一個 easing 方法。

/**
 *  執行補間動畫方法
 *
 * @param      {Number}    start     開始數值
 * @param      {Number}    end       結束數值
 * @param      {Number}    time      補間時間
 * @param      {Function}  callback  每幀回調
 * @param      {Function}  easing    緩動方法,默認勻速
 */
function animate(start, end, time, callback, easing = t => t) {
    let startTime = performance.now() // 設置開始的時間戳
    let differ = end - start // 拿到數值差值
    // 建立每幀以前要執行的函數
    function loop() {
        raf = requestAnimationFrame(loop) // 下一陣調用每幀以前要執行的函數
        const passTime = performance.now() - startTime // 獲取當前時間和開始時間差
        let per = passTime / time // 計算當前已過百分比
        if (per >= 1) { // 判讀若是已經執行
            per = 1 // 設置爲最後的狀態
            cancelAnimationFrame(raf) // 停掉動畫
        }
        const pass = differ * easing(per) // 經過已過期間百分比*開始結束數值差得出當前的數值
        callback(pass)
    }
    let raf = requestAnimationFrame(loop) // 下一陣調用每幀以前要執行的函數
}

測試一下,將咱們剛剛建立的 easing 方法傳進來

加速運動

let box = document.querySelector('.box')
animate(0, 500, 400, value => {
    box.style.transform = `translateX(${value}px)` // 將數值設置給 方塊 的 css 屬性 transform 屬性能夠控制元素在水平方向上的位移
}, easeIn)

加速後抖一抖

let box = document.querySelector('.box')
animate(0, 500, 400, value => {
    box.style.transform = `translateX(${value}px)` // 將數值設置給 方塊 的 css 屬性 transform 屬性能夠控制元素在水平方向上的位移
}, shake)

總結

這些只是 JavaScript 動畫基礎中的基礎,理解動畫的原理後,再作動畫就更駕輕就熟了。

市面上有不少JS動畫庫,你們能夠開箱即用。有一些是針對DOM操做的,也有一些是針對 JavaScript 對象。實現原理你都已經懂了。

上述代碼已發佈到Github: https://github.com/fanmingfei/animation-base

個人另外兩篇關於動畫的文章:

相關文章
相關標籤/搜索