轉自:http://www.zhangxinxu.com/wordpress/?p=3695css
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):小丸子,我要洗澡了,你能夠跟我一塊兒洗哦!
這是資源很是高效的一種利用方式。怎麼講呢?
腫麼樣?requestAnimationFrame我果真是萬千迷人的吧!!
耶!果真有料,不是看上去的平板電腦。
唷~誇得人家都很差意思了!
。。。。那你的兼容性如何?
個人兼容性啊~~ 孬,見下面~
Android設備壓根就不支持嘛!其餘設備基本上跟CSS3動畫的支持如出一轍嘛。
我說小美女,據我所知,CSS3 transition或animation動畫也是走的跟你同樣的繪製原理(補充於2013-10-09:根據本身後來的測試,發現,CSS3動畫在Tab切換回來的時候,動畫表現並不暫停;經過Chrome frames工具測試發現,Tab切換以後,計算渲染繪製都中止,Tab切換回來時彷佛經過內置JS計算了動畫位置實現重繪,形成動畫不暫停的感受)。可是人家的實現輕鬆不少啊,並且至關強大,那你還有個毛線用!你該想一想你一直鮮有人問津的緣由了!
基佬們,大家的眼界太狹隘了,觀點太膚淺的。首先從哲學宏觀講,事物存在必有其道理。所以,本大人確定是有價值的。
那你到說說你有納尼價值~~~
準備好了木有,待會兒說出來嚇死大家。聽好了,足足有3大點:
雖然說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也能支持哦!
耶耶耶!……
使用CSS3動畫能夠改變高寬,方位,角度,透明度等等。可是,就像六道帶土也有弱點同樣,CSS3動畫也有屬性鞭長莫及。比方說scrollTop值。若是咱們但願返回頂部是個平滑滾動效果,就目前而言,CSS3彷佛是無能爲力的。此時,仍是要JS出馬,勢必,我requestAnimationFrame大人就能夠大放異彩,萬衆矚目啦,哈哈哈哈哈哈哈~~
因爲CSS3動畫的貝塞爾曲線是一個標準3次方曲線(詳見:貝塞爾曲線與CSS3動畫、SVG和canvas的基情),所以,只能是:Linear, Sine, Quad, Cubic, Expo等,但對於Back, Bounce等緩動則只可觀望而不可褻玩焉。
下面這張圖瞅瞅,那些波瀾壯闊的曲線都是CSS3木有的~~
咋辦,咋辦?只能是JS實現啦,因而,本大人我requestAnimationFrame能夠再一次大放異彩啦,啊哈哈哈!
得意的太早了吧,這些動畫曲線看上去很複雜,偶們顯然駕馭不了了。
就知道大家這些基佬中看不中用。先給你們普及下緩動(Tween)知識吧:
每一個效果都分三個緩動方式,分別是(可採用後面的邪惡記憶法幫助記憶):
每週動畫效果都有其自身的算法。咱們都知道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以及上面的動畫算法,各類動畫效果所向披靡了,哈哈哈哈!!
您能夠狠狠地點擊這裏: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(); };
新年伊始,根據這篇翻譯文章一些測試說法,FireFox/Chrome瀏覽器對setInterval, setTimeout作了優化,頁面處於閒置狀態的時候,若是定時間隔小於1秒鐘(1000ms),則中止了定時器。與requestAnimationFrame有相似行爲。但若是時間間隔大於或等於1000ms,定時器依然執行,即便頁面最小化或非激活狀態。
參見下表:
項目 | setInterval | requestAnimationFrame |
---|---|---|
IE | 無影響 | 暫停 |
Safari | 無影響 | 暫停 |
Firefox | >=1s | 1s - 3s |
Chrome | >=1s | 暫停 |
Opera | 無影響 | 暫停 |