【轉】CSS3動畫那麼強,requestAnimationFrame還有毛線用?

轉自:http://www.zhangxinxu.com/wordpress/?p=3695css

1、喲,requestAnimationFrame, 新同窗,先自我介紹下

Hello, 你們好,我就是風姿卓越,萬種迷人的requestAnimationFrame,呵呵呵呵。很高興和你們見面,請多指教!html

。。。。css3

咳咳,你們不要一副不屑的樣子嘛。跟你講,我但是頗有用的。所謂人如其名,看我名字這麼長,表意爲「請求動畫幀」,明擺着一副很屌的樣子!git

。。。。按照這種說法,「櫻桃小丸子」就是櫻桃作的丸子咯,恩,看腦殼確實蠻像的~github

。。。。web

想到明天就是國慶大假,今天我就小人不記大人過。給大家來副震精的圖:算法

至關一部分的瀏覽器的顯示頻率是16.7ms, 就是上圖第一行的節奏,表現就是「我和你一步兩步三步四步往前走……」。若是咱們火力搞猛一點,例如搞個10ms setTimeout,就會是下面一行的模樣——每第三個圖形都沒法繪製(紅色箭頭指示),表現就是「我和你一步兩步 坑 四步往前走……」。canvas

國慶北京高速,最多每16.7s經過一輛車,結果,忽然插入一批setTimeout的軍車,強行要10s經過。顯然,這是超負荷的,要想順利進行,只能讓第三輛車直接消失(正如顯示繪製第三幀的丟失)。然,這是不現實的,因而就有了會堵車!segmentfault

一樣的,顯示器16.7ms刷新間隔以前發生了其餘繪製請求(setTimeout),致使全部第三幀丟失,繼而致使動畫斷續顯示(堵車的感受),這就是過分繪製帶來的問題。不只如此,這種計時器頻率的下降也會對電池使用壽命形成負面影響,並會下降其餘應用的性能。瀏覽器

這也是爲什麼setTimeout的定時器值推薦最小使用16.7ms的緣由(16.7 = 1000 / 60, 即每秒60幀)。

而我requestAnimationFrame就是爲了這個而出現的。我所作的事情很簡單,跟着瀏覽器的繪製走,若是瀏覽設備繪製間隔是16.7ms,那我就這個間隔繪製;若是瀏覽設備繪製間隔是10ms, 我就10ms繪製。這樣就不會存在過分繪製的問題,動畫不會掉幀,天然流暢的說~~

內部是這麼運做的:
瀏覽器(如頁面)每次要洗澡(重繪),就會通知我(requestAnimationFrame):小丸子,我要洗澡了,你能夠跟我一塊兒洗哦!

這是資源很是高效的一種利用方式。怎麼講呢?

  1. 就算不少個小丸子要一塊兒洗澡,瀏覽器只要通知一次就能夠了。而setTimeout貌似是多個獨立繪製。
  2. 頁面最小化了,或者被Tab切換關燈了。頁面是不會洗澡的,天然,小丸子也不會洗澡的(沒通知啊)。頁面繪製所有中止,資源高效利用。

腫麼樣?requestAnimationFrame我果真是萬千迷人的吧!!

耶!果真有料,不是看上去的平板電腦。

唷~誇得人家都很差意思了!

。。。。那你的兼容性如何?

個人兼容性啊~~ 孬,見下面~

Android設備壓根就不支持嘛!其餘設備基本上跟CSS3動畫的支持如出一轍嘛。

我說小美女,據我所知,CSS3 transition或animation動畫也是走的跟你同樣的繪製原理(補充於2013-10-09:根據本身後來的測試,發現,CSS3動畫在Tab切換回來的時候,動畫表現並不暫停;經過Chrome frames工具測試發現,Tab切換以後,計算渲染繪製都中止,Tab切換回來時彷佛經過內置JS計算了動畫位置實現重繪,形成動畫不暫停的感受)。可是人家的實現輕鬆不少啊,並且至關強大,那你還有個毛線用!你該想一想你一直鮮有人問津的緣由了!

2、反擊吧,requestAnimationFrame同窗

基佬們,大家的眼界太狹隘了,觀點太膚淺的。首先從哲學宏觀講,事物存在必有其道理。所以,本大人確定是有價值的。

那你到說說你有納尼價值~~~

準備好了木有,待會兒說出來嚇死大家。聽好了,足足有3大點:

1. 統一的向下兼容策略

雖然說CSS3實現動畫即高效又方便,可是對於PC瀏覽器,IE8, IE9之流,你想兼容實現某些動畫效果,比方說淡入淡出,敢問,你怎麼實現?

看大家那副呆若木雞的表情就知道了,IE10+ CSS3實現,IE9-之流JS setTimeout實現,我說累不累啊,兩套徹底不一樣的style. 你改下動畫時間是否是要改兩處?可是我requestAnimationFrame跟setTimeout很是相似,都是單回調,用法也相似。

var handle = setTimeout(renderLoop, PERIOD);
var handle = requestAnimationFrame(renderLoop);

我requestAnimationFrame調用一次只會重繪一次動畫,所以,若是想要實現聯繫動畫,就使用renderLoop反覆蹂躪我吧~

So,若是想要簡單的兼容,能夠這樣子

window.requestAnimFrame = (function(){
  return  window.requestAnimationFrame       ||
          window.webkitRequestAnimationFrame ||
          window.mozRequestAnimationFrame    ||
          function( callback ){
            window.setTimeout(callback, 1000 / 60);
          };
})();

可是呢,並非全部設備的繪製時間間隔是1000/60 ms, 以及上面並木有cancel相關方法,因此,就有下面這份更全面的兼容方法:

(function() {
    var lastTime = 0;
    var vendors = ['webkit', 'moz'];
    for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
        window.requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame'];
        window.cancelAnimationFrame = window[vendors[x] + 'CancelAnimationFrame'] ||    // Webkit中此取消方法的名字變了
                                      window[vendors[x] + 'CancelRequestAnimationFrame'];
    }

    if (!window.requestAnimationFrame) {
        window.requestAnimationFrame = function(callback, element) {
            var currTime = new Date().getTime();
            var timeToCall = Math.max(0, 16.7 - (currTime - lastTime));
            var id = window.setTimeout(function() {
                callback(currTime + timeToCall);
            }, timeToCall);
            lastTime = currTime + timeToCall;
            return id;
        };
    }
    if (!window.cancelAnimationFrame) {
        window.cancelAnimationFrame = function(id) {
            clearTimeout(id);
        };
    }
}());

而後,咱們就能夠以使用setTimeout的調調使用requestAnimationFrame方法啦,IE6也能支持哦!

耶耶耶!……

2. CSS3動畫不能應用全部屬性

使用CSS3動畫能夠改變高寬,方位,角度,透明度等等。可是,就像六道帶土也有弱點同樣,CSS3動畫也有屬性鞭長莫及。比方說scrollTop值。若是咱們但願返回頂部是個平滑滾動效果,就目前而言,CSS3彷佛是無能爲力的。此時,仍是要JS出馬,勢必,我requestAnimationFrame大人就能夠大放異彩,萬衆矚目啦,哈哈哈哈哈哈哈~~

3. CSS3支持的動畫效果有限

因爲CSS3動畫的貝塞爾曲線是一個標準3次方曲線(詳見:貝塞爾曲線與CSS3動畫、SVG和canvas的基情),所以,只能是:Linear, Sine, Quad, Cubic, Expo等,但對於Back, Bounce等緩動則只可觀望而不可褻玩焉。

下面這張圖瞅瞅,那些波瀾壯闊的曲線都是CSS3木有的~~

咋辦,咋辦?只能是JS實現啦,因而,本大人我requestAnimationFrame能夠再一次大放異彩啦,啊哈哈哈!

得意的太早了吧,這些動畫曲線看上去很複雜,偶們顯然駕馭不了了。

就知道大家這些基佬中看不中用。先給你們普及下緩動(Tween)知識吧:

  • Linear:無緩動效果
  • Quadratic:二次方的緩動(t^2)
  • Cubic:三次方的緩動(t^3)
  • Quartic:四次方的緩動(t^4)
  • Quintic:五次方的緩動(t^5)
  • Sinusoidal:正弦曲線的緩動(sin(t))
  • Exponential:指數曲線的緩動(2^t)
  • Circular:圓形曲線的緩動(sqrt(1-t^2))
  • Elastic:指數衰減的正弦曲線緩動
  • 超過範圍的三次方緩動((s+1)t^3 – st^2)
  • 指數衰減的反彈緩動

每一個效果都分三個緩動方式,分別是(可採用後面的邪惡記憶法幫助記憶):

  • easeIn:從0開始加速的緩動,想象OOXX進去,探路要花時間,所以確定是先慢後快的;
  • easeOut:減速到0的緩動,想象OOXX出來,確定定先快後慢的,以防掉出來;
  • easeInOut:前半段從0開始加速,後半段減速到0的緩動,想象OOXX進進出出,先慢後快而後再慢。

每週動畫效果都有其自身的算法。咱們都知道jQuery UI中就有緩動,As腳本也內置了緩動,其中的運動算法都是一致的。我特地弄了一份,哦呵呵呵~~

/*
 * Tween.js
 * t: current time(當前時間)
 * b: beginning value(初始值)
 * c: change in value(變化量)
 * d: duration(持續時間)
*/
var Tween = {
    Linear: function(t, b, c, d) { return c*t/d + b; },
    Quad: {
        easeIn: function(t, b, c, d) {
            return c * (t /= d) * t + b;
        },
        easeOut: function(t, b, c, d) {
            return -c *(t /= d)*(t-2) + b;
        },
        easeInOut: function(t, b, c, d) {
            if ((t /= d / 2) < 1) return c / 2 * t * t + b;
            return -c / 2 * ((--t) * (t-2) - 1) + b;
        }
    },
    Cubic: {
        easeIn: function(t, b, c, d) {
            return c * (t /= d) * t * t + b;
        },
        easeOut: function(t, b, c, d) {
            return c * ((t = t/d - 1) * t * t + 1) + b;
        },
        easeInOut: function(t, b, c, d) {
            if ((t /= d / 2) < 1) return c / 2 * t * t*t + b;
            return c / 2*((t -= 2) * t * t + 2) + b;
        }
    },
    Quart: {
        easeIn: function(t, b, c, d) {
            return c * (t /= d) * t * t*t + b;
        },
        easeOut: function(t, b, c, d) {
            return -c * ((t = t/d - 1) * t * t*t - 1) + b;
        },
        easeInOut: function(t, b, c, d) {
            if ((t /= d / 2) < 1) return c / 2 * t * t * t * t + b;
            return -c / 2 * ((t -= 2) * t * t*t - 2) + b;
        }
    },
    Quint: {
        easeIn: function(t, b, c, d) {
            return c * (t /= d) * t * t * t * t + b;
        },
        easeOut: function(t, b, c, d) {
            return c * ((t = t/d - 1) * t * t * t * t + 1) + b;
        },
        easeInOut: function(t, b, c, d) {
            if ((t /= d / 2) < 1) return c / 2 * t * t * t * t * t + b;
            return c / 2*((t -= 2) * t * t * t * t + 2) + b;
        }
    },
    Sine: {
        easeIn: function(t, b, c, d) {
            return -c * Math.cos(t/d * (Math.PI/2)) + c + b;
        },
        easeOut: function(t, b, c, d) {
            return c * Math.sin(t/d * (Math.PI/2)) + b;
        },
        easeInOut: function(t, b, c, d) {
            return -c / 2 * (Math.cos(Math.PI * t/d) - 1) + b;
        }
    },
    Expo: {
        easeIn: function(t, b, c, d) {
            return (t==0) ? b : c * Math.pow(2, 10 * (t/d - 1)) + b;
        },
        easeOut: function(t, b, c, d) {
            return (t==d) ? b + c : c * (-Math.pow(2, -10 * t/d) + 1) + b;
        },
        easeInOut: function(t, b, c, d) {
            if (t==0) return b;
            if (t==d) return b+c;
            if ((t /= d / 2) < 1) return c / 2 * Math.pow(2, 10 * (t - 1)) + b;
            return c / 2 * (-Math.pow(2, -10 * --t) + 2) + b;
        }
    },
    Circ: {
        easeIn: function(t, b, c, d) {
            return -c * (Math.sqrt(1 - (t /= d) * t) - 1) + b;
        },
        easeOut: function(t, b, c, d) {
            return c * Math.sqrt(1 - (t = t/d - 1) * t) + b;
        },
        easeInOut: function(t, b, c, d) {
            if ((t /= d / 2) < 1) return -c / 2 * (Math.sqrt(1 - t * t) - 1) + b;
            return c / 2 * (Math.sqrt(1 - (t -= 2) * t) + 1) + b;
        }
    },
    Elastic: {
        easeIn: function(t, b, c, d, a, p) {
            var s;
            if (t==0) return b;
            if ((t /= d) == 1) return b + c;
            if (typeof p == "undefined") p = d * .3;
            if (!a || a < Math.abs(c)) {
                s = p / 4;
                a = c;
            } else {
                s = p / (2 * Math.PI) * Math.asin(c / a);
            }
            return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p)) + b;
        },
        easeOut: function(t, b, c, d, a, p) {
            var s;
            if (t==0) return b;
            if ((t /= d) == 1) return b + c;
            if (typeof p == "undefined") p = d * .3;
            if (!a || a < Math.abs(c)) {
                a = c; 
                s = p / 4;
            } else {
                s = p/(2*Math.PI) * Math.asin(c/a);
            }
            return (a * Math.pow(2, -10 * t) * Math.sin((t * d - s) * (2 * Math.PI) / p) + c + b);
        },
        easeInOut: function(t, b, c, d, a, p) {
            var s;
            if (t==0) return b;
            if ((t /= d / 2) == 2) return b+c;
            if (typeof p == "undefined") p = d * (.3 * 1.5);
            if (!a || a < Math.abs(c)) {
                a = c; 
                s = p / 4;
            } else {
                s = p / (2  *Math.PI) * Math.asin(c / a);
            }
            if (t < 1) return -.5 * (a * Math.pow(2, 10* (t -=1 )) * Math.sin((t * d - s) * (2 * Math.PI) / p)) + b;
            return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p ) * .5 + c + b;
        }
    },
    Back: {
        easeIn: function(t, b, c, d, s) {
            if (typeof s == "undefined") s = 1.70158;
            return c * (t /= d) * t * ((s + 1) * t - s) + b;
        },
        easeOut: function(t, b, c, d, s) {
            if (typeof s == "undefined") s = 1.70158;
            return c * ((t = t/d - 1) * t * ((s + 1) * t + s) + 1) + b;
        },
        easeInOut: function(t, b, c, d, s) {
            if (typeof s == "undefined") s = 1.70158; 
            if ((t /= d / 2) < 1) return c / 2 * (t * t * (((s *= (1.525)) + 1) * t - s)) + b;
            return c / 2*((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2) + b;
        }
    },
    Bounce: {
        easeIn: function(t, b, c, d) {
            return c - Tween.Bounce.easeOut(d-t, 0, c, d) + b;
        },
        easeOut: function(t, b, c, d) {
            if ((t /= d) < (1 / 2.75)) {
                return c * (7.5625 * t * t) + b;
            } else if (t < (2 / 2.75)) {
                return c * (7.5625 * (t -= (1.5 / 2.75)) * t + .75) + b;
            } else if (t < (2.5 / 2.75)) {
                return c * (7.5625 * (t -= (2.25 / 2.75)) * t + .9375) + b;
            } else {
                return c * (7.5625 * (t -= (2.625 / 2.75)) * t + .984375) + b;
            }
        },
        easeInOut: function(t, b, c, d) {
            if (t < d / 2) {
                return Tween.Bounce.easeIn(t * 2, 0, c, d) * .5 + b;
            } else {
                return Tween.Bounce.easeOut(t * 2 - d, 0, c, d) * .5 + c * .5 + b;
            }
        }
    }
}
Math.tween = Tween;

因而,藉助大人我requestAnimationFrame以及上面的動畫算法,各類動畫效果所向披靡了,哈哈哈哈!!

6

您能夠狠狠地點擊這裏:requestAnimationFrame+Tween緩動小球落地效果

相關源代碼能夠參見demo頁面源代碼——直接右鍵便可以。核心動畫部分的腳本是:

funFall = function() {
    var start = 0, during = 100;
    var _run = function() {
         start++;
         var top = Tween.Bounce.easeOut(start, objBall.top, 500 - objBall.top, during);
         ball.css("top", top);
         shadowWithBall(top);    // 投影跟隨小球的動
         if (start < during) requestAnimationFrame(_run);
    };
    _run();
};

補充於2014-02-08

新年伊始,根據這篇翻譯文章一些測試說法,FireFox/Chrome瀏覽器對setInterval, setTimeout作了優化,頁面處於閒置狀態的時候,若是定時間隔小於1秒鐘(1000ms),則中止了定時器。與requestAnimationFrame有相似行爲。但若是時間間隔大於或等於1000ms,定時器依然執行,即便頁面最小化或非激活狀態。

參見下表:

項目 setInterval requestAnimationFrame
IE 無影響 暫停
Safari 無影響 暫停
Firefox >=1s 1s - 3s
Chrome >=1s 暫停
Opera 無影響 暫停

相關連接

相關文章
相關標籤/搜索