JavaScript 動畫原理淺析

FerBorrie 7年前發布 | 12K 次閱讀 JavaScript開發 JavaScript

動畫

通常,框架會為你處理動畫。但是,你可能想知道僅僅用javascript怎么來實現動畫,和可能出現的一些問題。理解這項技術對于創建復雜的動畫是很有幫助的,即使在在框架的幫助下。

動畫基礎

javascript的動畫是通過周期性的改變DOM元素的style 或者 canvas 的對象。

這整個的動畫過程被分成了很小的碎片,每一個碎片被定時器調用。因為定時器的周期非常短,所以動畫看起來是連續的。

偽代碼:

var id = setInterval(function(){
    /*當前顯示幀*/
    if(/*完成*/) clearInterval(id)
}, 10)

上面代碼每一幀的間隔是 10ms ,意味著每秒鐘有100幀。

在大多數的javascript框架中 10-15ms的delay是默認的。越短的延時讓動畫看起來更加流暢,但是只有在瀏覽器足夠快的時候,每一步的動畫才會準時運行。

如果動畫需要許多計算,CPU可能會100%的負載,動畫就會變得遲緩。這種情況下,delay就應該被增加。例如,delay 40ms 就是每秒25幀,接近24幀的電影標準。

setInterval 而不是用 setTimout

我們使用 setInterval ,而不是遞歸的使用 setTimeout ,是因為我們想要一幀一個delay,而不是所有幀之間有一個固定的delay.查閱 Understanding timers: setTimeout and setInterval 來了解 setInterval 和遞歸的 setTimeout 之間到底有什么不同。(譯者:經測試最新的chrome > 56中,setInterval的行為跟本文中描述的不同。當函數執行時間超過了delay時間,下一個函數不會馬上運行,仍然會等一個delay的間隔,再執行。但本文仍有參考價值)

例子

例如,一個元素移動通過改變 element.stye.left 從0到100px。每10ms改變1px。

<html>
<head>
<link type="text/css" rel="stylesheet" href="/files/tutorial/browser/animatio/animate.css">
<script>
    function move(elem) {
        var left = 0;
        function frame(){
            left++ // 更新參數
            elem.style.left = left + 'px' // 顯示幀
            if(left === 100) //檢查結束條件
            clearInterval(id)
        }
        var id = setInterval(frame, 10) //沒10ms繪制一次
    }
</script>
</head>
<body>
<div onclick="move(this.children[0])" class="example_path">
<div class="example_block"></div>
</div>
</body>
</html>

在新的窗口打開

動畫重構

為了讓動畫更加通用,我們介紹下面的一些參數:

  • delay

    每一幀之間的間隔(ms).例如,10ms

  • duration

    整個動畫完成需要的時間(ms)。例如:1000ms

當動畫開始的時候,我們也可以用:

  • start 動畫開始的時間, start = new Date
    動畫過程的核心,每一幀我們需要計算:

  • timePassed

    從動畫開始所經過的時間(ms)。

從0到動畫(duration)結束.但是偶爾可能會超過結束時間,因為瀏覽器的計時器并不準確。

  • progress

    已經過去的動畫時間作為分子,計算每一幀通過公式 timePassed/duration 。值得范圍通常是0到1。

例如,progress的值為0.5就是說動畫時間(duration)已經過去了一半。

  • delta(progress)

    一個返回當前動畫進度的函數。

例如,我們讓高度屬性從0%變化到100%。

我們可以讓動畫均勻的顯示,這樣動畫進度看起來就是線性的。

Mapping:

->progress = 0 -> height = 0%

->progress = 0.2 -> height = 20%

->progress = 0.5->height = 50%

->progress = 0.8 -> height = 80%

->progress = 1 -> height = 100%

但是我們可能想讓動畫緩慢的開始然后再加速。這樣的話經過一半的動畫時間高度可能只有25%

,然后逐漸加速到100%。

Mapping:

->progress = 0 -> height = 0%

->progress = 0.2 -> height = 4%

->progress = 0.5->height = 25%

->progress = 0.8 -> height = 64%

->progress = 1 -> height = 100%

delta(progress) 是一個映射動畫進度增量的函數

動畫進度不是一個高度,而是一個數字,通常在0到1之間。

這篇文章會用一些例子進一步討論幾種增量函數

  • step(delta)

    這個函數是實際上用來做這件事的函數。

它計算出增量的結果并且應用它。

對于這個高度的例子,他們可能是:

function state(delta) {
    elem.style.height = 100*delta + "%"
}

到現在為止幾個重要的參數是:

->delay是 setInterval 的第二個參數。

->duration是動畫完成需要的時間。

->progress是動畫已經經過的時間,除以duration使它的值在0到1之間。

->delta通過當前的時間,計算當前的動畫進度。

->step做了視覺上(?)的工作。它獲得當前的動畫進度,并且把它應用在元素上。

通用的動畫

讓我們把上面討論的簡單的寫成一個可擴展的動畫核心。

下面的動畫函數執行時間管理并且把工作分配給delta和step:

function animate(opts) {
    var start = new Date;
    var id = setInterval(function(){
        var timePassed = new Date - start;
        var progress = timePassed / opts.duration;
        if(progress > 1) progress = 1;
        var delta = opts.delta(progress)
        opts.step(delta)
        if(progress == 1) {
            clearInterval(id)
        }
    }, opts.delay || 10)
}

參數對象應該包含以下的一些動畫屬性:

->delay

->duration

->function delta

->function step

這個算法完全遵循上面的描述

Example

讓我們基于這個來創建一個移動的動畫

function move(element, delta, duration){
    var to = 500;
    animate({
        delay: 10,
        duration: duration || 1000,
        delta: delta,
        step: function(delta){
            element.style.left = to * delta + 'px'
        }
    })
}

它把工作指派給 animate ,給animate傳入了delay,用戶提供的duration, delta, 和 step。

delta = function(p) { return p}

意味著動畫進程一直是均勻的

step

用一個簡單的公式映射0..1,delta返回一個進度值 0..to。把這些結果應用到element.style.left。

用法:

<div onclick="move(this.children[0], function(p) {return p})"

class="example_path"> <div class="example_block"></div> </div></code></pre>

數學,進度增量函數

動畫就是是根據給定的規則,一直改變屬性。在javascript動畫中,這個規則就是delta函數來實現的。

不同的deltas使動畫的速度,加速度和其他的參數表現出各種各樣的方式。

數學公式通常被用在這里。但是它們對于只做web編程和忘記學校里的數學的人來說,可能很陌生。在這個章節,我們將瀏覽很多的受歡迎的公式并看一下它們是如何工作的。

動畫運動的例子,提供不同的delta.

線性 delta

function linear(progress){
    return progress;
}

水平方向指的是時間進度,垂直方向指的是動畫進程。

我們已經能看見了。線性的delta使動畫以固定的速度進行。

Power of n

也是一個簡單的例子。delta是progress的n次方。例如4次,3次函數等。

例如2次函數

function quad(progress) {
    return Math.pow(progress, 2)
}

圖形

增加加速度的影響。例如,下面這個圖片是5次方。

Circ: 圓的一部分

函數:

function circ(progress) {
    return 1 - Math.sin(Math.acos(progress))
}

圖像

Back: the bow function

這個函數向弓一樣工作:首先我們"拉開工,然后發射出去"。

不像先前的函數,它會依賴于一個附加的參數 x ,這就是“彈性系數”。

它定義了“拉弓”的距離。

代碼是:

function back(progress, x) {
    return Math.pow(progress, 2) * ((x + 1) * progress - x)
}

圖像x=1.5

bounce(彈跳)

想象一下我們釋放一個球,它掉在地上,然后彈跳幾次,最后停止。

bounce事實上做了相反的事情。屬性將會一直改變知道它達到目標點。

這個函數比之前要復雜一些,也沒有簡單的數學公式。

function bounce(progress) {
    for(var a = 0, b=1, result; 1; a+=b, b /= 2) {
        if (progress >= (7-4*a) / 11){
            return -Math.pow(11 - 6 * a - 11 * progress) / 4, 2) + Math.pow(b, 2)
        }
    }
}

*這段代碼曲子 MooTools.FX.Transitions.據我所知,仍然有其他實現bounce的算法。

Elastic 彈性

這個函數也依賴于額外的參數x,x定義了初始范圍。

function elastic(progress, x){
    return Math.pow(2, 10 * (progress - 1)) * Math.cos(20 * Math.PI * x/3 *progress)
}

圖像 x=1.5

在這個例子中,為了讓動畫更加平滑,時間是2s.

反向函數(Reverse functions)

一個javascript框架經常會提供的一種delta函數。

它們的直接使用被稱作 easeIn.

偶爾的時候,需要以時間倒退的方式來展示動畫。這就叫做 easeout 被'time-reversing'delta來實現。

第一次翻譯點東西,質量不好,主要是做記錄用。里面有很多比較'術語'的,不好翻,最好看原文。后面我也會繼續修改。

 

來自: http://javascript.info/tutori...

 

 本文由用戶 FerBorrie 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!