JavaScript動畫實現初探

子回發表於2015-03-23

前言

現如今,許多頁面上均有一些動畫效果。適當的動畫效果可以在一定程度上提高頁面的美觀度,具有提示效果的動畫可以增強頁面的易用性。

實現頁面動畫的途徑有兩種。一種是通過操作JavaScript間接操作CSS樣式,每隔一段時間更新一次;一種是直接通過CSS定義動畫。第二種方法在CSS3成熟之後被廣泛採用。在本文中,我們討論第一種方法的原理和實現。

JavaScript動畫實現原理

首先我們需要知道兩個重要的概念,動畫時間程式和動畫效果程式。

動畫時間程式指從時間上看動畫的完成度,是一個[0, 1]之間的數字。假設動畫於時間戳t1開始,要在t2結束,當前時間戳為t,那麼該動畫目前的時間程式為(t-t1)/(t2-t1)。如果你不能理解,我建議你用紙筆畫出來。理解這一概念對理解本文至關重要。

動畫效果程式指被動畫的屬性值當前的增量。假設我們要將#el元素的CSS left 屬性從100px變到200px,當前已經變到了130px,那麼該動畫目前的效果程式為130px - 100px = 30px

假設動畫時間程式和動畫效果程式都是線性的。那麼如果知道了動畫時間程式,一定可以得到動畫效果程式。

根據這個解釋,我們很快可以編寫出一個線性的動畫。

(function() {
      var begin, // 開始動畫的時間
        el, start, end, duration; 
      var INTERVAL = 13;

      function now() {
        return (new Date).getTime();
      }

      /**
       * 執行一步動畫(更新屬性)
       */
      function _animLeft() {
        var pos = (now() - begin) / duration;
        if (pos >= 1.0) {
          return false;
        }
        return !!(el.style.left = start + (end - start) * pos);
      }

      /**
       * 對一個DOM執行動畫,left從_start到_end,執行時間為
       * _duration毫秒。
       * 
       * @param  {object} _el       要執行動畫的DOM節點
       * @param  {integer} _start   left的起始值
       * @param  {integer} _end     left的最終值
       * @param  {integer} _duration  動畫執行時間
       */
      function animLeft(_el, _start, _end, _duration) {
        stopped = false;
        begin = now();
        el = _el;
        start = _start;
        end = _end;
        duration = _duration || 1000;

        var step = function() {
          if (_animLeft()) {
            setTimeout(step, INTERVAL);
          }
        };
        setTimeout(step, 0);
      }

      window.animLeft = animLeft;
    })();

    animLeft(
      document.getElementById('el'),
      100,
      200
    )

JSBin

easing

很多時候,我們需要的動畫並非線性的。所謂非線性,從直觀上看,就是動畫速度隨著時間會產生變化。那麼如何實現變速的動畫呢?

由前所述,我們知道通過控制動畫的時間程式就相當於控制動畫的效果程式。隨著真實世界的時間程式推移,動畫的時間程式跟著推移,從而控制動畫的效果程式推移。那麼,我們可以通過修改真實世界的時間程式和動畫的時間程式間的對映關係,從而控制動畫程式。如果你感到困惑,沒關係,請看下圖:

這是線性動畫中,真實世界的時間程式和動畫程式的對映關係。接下來,我們將其進行變換

這條曲線實際上是函式y = x * x的影像。可以看到,兩個曲線的定義域和值域並沒有變化。曲線的斜率就是動畫的速率。接下來我們將兩張圖重疊在一起做一個對比。

在真實世界的時間進行到x0的時候,動畫程式原本應該進行到y0,在進行變換之後,只進行到y1。到最後,百川歸海,兩條線交匯於點(1, 1)。這裡,y = x * x變換函式(easing function)

我們修改一下上面的例子,讓動畫變成非線性的。

function ease(time) {
      return time * time;
    }

    /**
     * 執行一步動畫(更新屬性)
     */
    function _animLeft() {
      var pos = (now() - begin) / duration;
      if (pos >= 1.0) {
        return false;
      }
      pos = ease(pos);
      return !!(el.style.left = (start + (end - start) * pos) + "px");
    }

JSBin

我們可以在jQuery的程式碼中看到這樣的函式。

jQuery.easing = {
      linear: function( p ) {
        return p;
      },
      swing: function( p ) {
        return 0.5 - Math.cos( p * Math.PI ) / 2;
      }
    };

因此,你可以往jQuery.easing裡面新增easing function,使得jQuery支援新的動畫速率控制方法。注意,easing function的定義域和值域必須都為[0, 1]。

 jQuery.easing.myEasing = function( p ) { return ... }

總結

本文介紹了JavaScript動畫的最基本的原理。

JavaScript動畫實質上也是通過操作CSS去執行動畫。動畫的時間程式可以決定動畫的效果程式。通過操作真實世界的時間程式和動畫的時間程式之間的關係,我們可以將線性動畫變換成非線性的動畫。

相關文章