requestAnimationFrame

在Web應(yīng)用中,實(shí)現(xiàn)動(dòng)畫效果的方法比較多川蒙,Javascript 中可以通過(guò)定時(shí)器 setTimeout 來(lái)實(shí)現(xiàn),css3 可以使用 transition 和 animation 來(lái)實(shí)現(xiàn)鳍徽,html5 中的 canvas 也可以實(shí)現(xiàn)蔫巩。除此之外,html5 還提供一個(gè)專門用于請(qǐng)求動(dòng)畫的API叶组,那就是 requestAnimationFrame拯田,顧名思義就是請(qǐng)求動(dòng)畫幀。 為了深入理解 requestAnimationFrame 背后的原理甩十,我們首先需要了解一下與之相關(guān)的幾個(gè)概念:

1船庇、屏幕刷新頻率
即圖像在屏幕上更新的速度,也即屏幕上的圖像每秒鐘出現(xiàn)的次數(shù)侣监,它的單位是赫茲(Hz)溢十。 對(duì)于一般筆記本電腦,這個(gè)頻率大概是60Hz达吞, 可以在桌面上右鍵->屏幕分辨率->高級(jí)設(shè)置->監(jiān)視器 中查看和設(shè)置张弛。這個(gè)值的設(shè)定受屏幕分辨率、屏幕尺寸和顯卡的影響,原則上設(shè)置成讓眼睛看著舒適的值都行吞鸭。

市面上常見的顯示器有兩種寺董,即CRT和LCD, CRT就是傳統(tǒng)顯示器刻剥,LCD就是我們常說(shuō)的液晶顯示器遮咖。

CRT是一種使用陰極射線管的顯示器,屏幕上的圖形圖像是由一個(gè)個(gè)因電子束擊打而發(fā)光的熒光點(diǎn)組成造虏,由于顯像管內(nèi)熒光粉受到電子束擊打后發(fā)光的時(shí)間很短御吞,所以電子束必須不斷擊打熒光粉使其持續(xù)發(fā)光。電子束每秒擊打熒光粉的次數(shù)就是屏幕刷新頻率漓藕。

而對(duì)于LCD來(lái)說(shuō)陶珠,則不存在刷新頻率的問(wèn)題,它根本就不需要刷新享钞。因?yàn)長(zhǎng)CD中每個(gè)像素都在持續(xù)不斷地發(fā)光揍诽,直到不發(fā)光的電壓改變并被送到控制器中,所以LCD不會(huì)有電子束擊打熒光粉而引起的閃爍現(xiàn)象栗竖。

因此暑脆,當(dāng)你對(duì)著電腦屏幕什么也不做的情況下,顯示器也會(huì)以每秒60次的頻率正在不斷的更新屏幕上的圖像狐肢。為什么你感覺不到這個(gè)變化添吗? 那是因?yàn)槿说难劬τ幸曈X停留效應(yīng),即前一副畫面留在大腦的印象還沒消失份名,緊接著后一副畫面就跟上來(lái)了碟联,這中間只間隔了16.7ms(1000/60≈16.7), 所以會(huì)讓你誤以為屏幕上的圖像是靜止不動(dòng)的同窘。而屏幕給你的這種感覺是對(duì)的玄帕,試想一下,如果刷新頻率變成1次/秒想邦,屏幕上的圖像就會(huì)出現(xiàn)嚴(yán)重的閃爍裤纹,這樣就很容易引起眼睛疲勞、酸痛和頭暈?zāi)垦5劝Y狀丧没。

2鹰椒、動(dòng)畫原理
根據(jù)上面的原理我們知道,你眼前所看到圖像正在以每秒60次的頻率刷新呕童,由于刷新頻率很高漆际,因此你感覺不到它在刷新。而動(dòng)畫本質(zhì)就是要讓人眼看到圖像被刷新而引起變化的視覺效果夺饲,這個(gè)變化要以連貫的奸汇、平滑的方式進(jìn)行過(guò)渡施符。 那怎么樣才能做到這種效果呢?

刷新頻率為60Hz的屏幕每16.7ms刷新一次擂找,我們?cè)谄聊幻看嗡⑿虑按亮撸瑢D像的位置向左移動(dòng)一個(gè)像素,即1px贯涎。這樣一來(lái)听哭,屏幕每次刷出來(lái)的圖像位置都比前一個(gè)要差1px,因此你會(huì)看到圖像在移動(dòng)塘雳;由于我們?nèi)搜鄣囊曈X停留效應(yīng)陆盘,當(dāng)前位置的圖像停留在大腦的印象還沒消失,緊接著圖像又被移到了下一個(gè)位置败明,因此你才會(huì)看到圖像在流暢的移動(dòng)隘马,這就是視覺效果上形成的動(dòng)畫。

3肩刃、setTimeout
理解了上面的概念以后祟霍,我們不難發(fā)現(xiàn)杏头,setTimeout 其實(shí)就是通過(guò)設(shè)置一個(gè)間隔時(shí)間來(lái)不斷的改變圖像的位置盈包,從而達(dá)到動(dòng)畫效果的。但我們會(huì)發(fā)現(xiàn)醇王,利用seTimeout實(shí)現(xiàn)的動(dòng)畫在某些低端機(jī)上會(huì)出現(xiàn)卡頓呢燥、抖動(dòng)的現(xiàn)象。 這種現(xiàn)象的產(chǎn)生有兩個(gè)原因:

setTimeout的執(zhí)行時(shí)間并不是確定的寓娩。在Javascript中叛氨, setTimeout 任務(wù)被放進(jìn)了異步隊(duì)列中,只有當(dāng)主線程上的任務(wù)執(zhí)行完以后棘伴,才會(huì)去檢查該隊(duì)列里的任務(wù)是否需要開始執(zhí)行寞埠,因此 setTimeout 的實(shí)際執(zhí)行時(shí)間一般要比其設(shè)定的時(shí)間晚一些。

刷新頻率受屏幕分辨率和屏幕尺寸的影響焊夸,因此不同設(shè)備的屏幕刷新頻率可能會(huì)不同仁连,而 setTimeout只能設(shè)置一個(gè)固定的時(shí)間間隔,這個(gè)時(shí)間不一定和屏幕的刷新時(shí)間相同阱穗。

以上兩種情況都會(huì)導(dǎo)致setTimeout的執(zhí)行步調(diào)和屏幕的刷新步調(diào)不一致饭冬,從而引起丟幀現(xiàn)象。 那為什么步調(diào)不一致就會(huì)引起丟幀呢揪阶?

首先要明白昌抠,setTimeout的執(zhí)行只是在內(nèi)存中對(duì)圖像屬性進(jìn)行改變,這個(gè)變化必須要等到屏幕下次刷新時(shí)才會(huì)被更新到屏幕上鲁僚。如果兩者的步調(diào)不一致炊苫,就可能會(huì)導(dǎo)致中間某一幀的操作被跨越過(guò)去裁厅,而直接更新下一幀的圖像。假設(shè)屏幕每隔16.7ms刷新一次侨艾,而setTimeout每隔10ms設(shè)置圖像向左移動(dòng)1px姐直, 就會(huì)出現(xiàn)如下繪制過(guò)程:

第0ms: 屏幕未刷新,等待中蒋畜,setTimeout也未執(zhí)行声畏,等待中;

第10ms: 屏幕未刷新姻成,等待中插龄,setTimeout開始執(zhí)行并設(shè)置圖像屬性left=1px;

第16.7ms: 屏幕開始刷新科展,屏幕上的圖像向左移動(dòng)了1px均牢, setTimeout 未執(zhí)行,繼續(xù)等待中才睹;

第20ms: 屏幕未刷新徘跪,等待中,setTimeout開始執(zhí)行并設(shè)置left=2px;

第30ms: 屏幕未刷新琅攘,等待中垮庐,setTimeout開始執(zhí)行并設(shè)置left=3px;

第33.4ms:屏幕開始刷新,屏幕上的圖像向左移動(dòng)了3px坞琴, setTimeout未執(zhí)行哨查,繼續(xù)等待中;

從上面的繪制過(guò)程中可以看出剧辐,屏幕沒有更新left=2px的那一幀畫面寒亥,圖像直接從1px的位置跳到了3px的的位置,這就是丟幀現(xiàn)象荧关,這種現(xiàn)象就會(huì)引起動(dòng)畫卡頓溉奕。

4、requestAnimationFrame
與setTimeout相比忍啤,requestAnimationFrame最大的優(yōu)勢(shì)是由系統(tǒng)來(lái)決定回調(diào)函數(shù)的執(zhí)行時(shí)機(jī)加勤。具體一點(diǎn)講,如果屏幕刷新率是60Hz,那么回調(diào)函數(shù)就每16.7ms被執(zhí)行一次檀轨,如果刷新率是75Hz胸竞,那么這個(gè)時(shí)間間隔就變成了1000/75=13.3ms,換句話說(shuō)就是参萄,requestAnimationFrame的步伐跟著系統(tǒng)的刷新步伐走卫枝。它能保證回調(diào)函數(shù)在屏幕每一次的刷新間隔中只被執(zhí)行一次,這樣就不會(huì)引起丟幀現(xiàn)象讹挎,也不會(huì)導(dǎo)致動(dòng)畫出現(xiàn)卡頓的問(wèn)題校赤。

這個(gè)API的調(diào)用很簡(jiǎn)單吆玖,如下所示:

var progress = 0;

//回調(diào)函數(shù)

function render() {

progress += 1; //修改圖像的位置


if (progress < 100) {

       //在動(dòng)畫沒有結(jié)束前,遞歸渲染

       window.requestAnimationFrame(render);

}

}

//第一幀渲染

window.requestAnimationFrame(render);

除此之外马篮,requestAnimationFrame還有以下兩個(gè)優(yōu)勢(shì):

CPU節(jié)能:使用setTimeout實(shí)現(xiàn)的動(dòng)畫沾乘,當(dāng)頁(yè)面被隱藏或最小化時(shí),setTimeout 仍然在后臺(tái)執(zhí)行動(dòng)畫任務(wù)浑测,由于此時(shí)頁(yè)面處于不可見或不可用狀態(tài)翅阵,刷新動(dòng)畫是沒有意義的,完全是浪費(fèi)CPU資源迁央。而requestAnimationFrame則完全不同掷匠,當(dāng)頁(yè)面處理未激活的狀態(tài)下,該頁(yè)面的屏幕刷新任務(wù)也會(huì)被系統(tǒng)暫停岖圈,因此跟著系統(tǒng)步伐走的requestAnimationFrame也會(huì)停止渲染讹语,當(dāng)頁(yè)面被激活時(shí),動(dòng)畫就從上次停留的地方繼續(xù)執(zhí)行蜂科,有效節(jié)省了CPU開銷顽决。

函數(shù)節(jié)流:在高頻率事件(resize,scroll等)中,為了防止在一個(gè)刷新間隔內(nèi)發(fā)生多次函數(shù)執(zhí)行导匣,使用requestAnimationFrame可保證每個(gè)刷新間隔內(nèi)才菠,函數(shù)只被執(zhí)行一次,這樣既能保證流暢性逐抑,也能更好的節(jié)省函數(shù)執(zhí)行的開銷鸠儿。一個(gè)刷新間隔內(nèi)函數(shù)執(zhí)行多次時(shí)沒有意義的屹蚊,因?yàn)轱@示器每16.7ms刷新一次厕氨,多次繪制并不會(huì)在屏幕上體現(xiàn)出來(lái)。

5汹粤、優(yōu)雅降級(jí)
由于requestAnimationFrame目前還存在兼容性問(wèn)題命斧,而且不同的瀏覽器還需要帶不同的前綴。因此需要通過(guò)優(yōu)雅降級(jí)的方式對(duì)requestAnimationFrame進(jìn)行封裝嘱兼,優(yōu)先使用高級(jí)特性国葬,然后再根據(jù)不同瀏覽器的情況進(jìn)行回退,直止只能使用setTimeout的情況芹壕。下面的代碼就是有人在github上提供的polyfill汇四,詳細(xì)介紹請(qǐng)參考github代碼 requestAnimationFrame(https://github.com/darius/requestAnimationFrame

if (!Date.now)

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

(function() {

'use strict';



var vendors = ['webkit', 'moz'];

for (var i = 0; i < vendors.length && !window.requestAnimationFrame; ++i) {

    var vp = vendors[i];

    window.requestAnimationFrame = window[vp+'RequestAnimationFrame'];

    window.cancelAnimationFrame = (window[vp+'CancelAnimationFrame']

                               || window[vp+'CancelRequestAnimationFrame']);

}

if (/iP(ad|hone|od).*OS 6/.test(window.navigator.userAgent) // iOS6 is buggy

    || !window.requestAnimationFrame || !window.cancelAnimationFrame) {

    var lastTime = 0;

    window.requestAnimationFrame = function(callback) {

        var now = Date.now();

        var nextTime = Math.max(lastTime + 16, now);

        return setTimeout(function() { callback(lastTime = nextTime); },

                          nextTime - now);

    };

    window.cancelAnimationFrame = clearTimeout;

}

}());

http://www.cnblogs.com/onepixel/p/7078617.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市踢涌,隨后出現(xiàn)的幾起案子通孽,更是在濱河造成了極大的恐慌,老刑警劉巖睁壁,帶你破解...
    沈念sama閱讀 211,290評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件背苦,死亡現(xiàn)場(chǎng)離奇詭異互捌,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)行剂,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門秕噪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人厚宰,你說(shuō)我怎么就攤上這事腌巾。” “怎么了铲觉?”我有些...
    開封第一講書人閱讀 156,872評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵壤躲,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我备燃,道長(zhǎng)碉克,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,415評(píng)論 1 283
  • 正文 為了忘掉前任并齐,我火速辦了婚禮漏麦,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘况褪。我一直安慰自己撕贞,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,453評(píng)論 6 385
  • 文/花漫 我一把揭開白布测垛。 她就那樣靜靜地躺著捏膨,像睡著了一般。 火紅的嫁衣襯著肌膚如雪食侮。 梳的紋絲不亂的頭發(fā)上号涯,一...
    開封第一講書人閱讀 49,784評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音锯七,去河邊找鬼链快。 笑死,一個(gè)胖子當(dāng)著我的面吹牛眉尸,可吹牛的內(nèi)容都是我干的域蜗。 我是一名探鬼主播,決...
    沈念sama閱讀 38,927評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼噪猾,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼霉祸!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起袱蜡,我...
    開封第一講書人閱讀 37,691評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤丝蹭,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后戒劫,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體半夷,經(jīng)...
    沈念sama閱讀 44,137評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡婆廊,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,472評(píng)論 2 326
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了巫橄。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片淘邻。...
    茶點(diǎn)故事閱讀 38,622評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖湘换,靈堂內(nèi)的尸體忽然破棺而出宾舅,到底是詐尸還是另有隱情,我是刑警寧澤彩倚,帶...
    沈念sama閱讀 34,289評(píng)論 4 329
  • 正文 年R本政府宣布筹我,位于F島的核電站,受9級(jí)特大地震影響帆离,放射性物質(zhì)發(fā)生泄漏蔬蕊。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,887評(píng)論 3 312
  • 文/蒙蒙 一哥谷、第九天 我趴在偏房一處隱蔽的房頂上張望岸夯。 院中可真熱鬧,春花似錦们妥、人聲如沸猜扮。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)旅赢。三九已至,卻和暖如春惑惶,著一層夾襖步出監(jiān)牢的瞬間煮盼,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工集惋, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留孕似,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,316評(píng)論 2 360
  • 正文 我出身青樓刮刑,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親养渴。 傳聞我的和親對(duì)象是個(gè)殘疾皇子雷绢,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,490評(píng)論 2 348