requestAnimationFrame用法優(yōu)化源碼解析
介紹
什么是requestAnimationFrame
requestAnimationFrame
是瀏覽器用于定時(shí)循環(huán)操作的一個(gè)API,通常用于動(dòng)畫(huà)和游戲開(kāi)發(fā)。它會(huì)把每一幀中的所有DOM操作集中起來(lái),在重繪之前一次性更新,并且關(guān)聯(lián)到瀏覽器的重繪操作。
為什么使用requestAnimationFrame而不是setTimeout或setInterval
與setTimeout
或setInterval
相比,requestAnimationFrame
具有以下優(yōu)勢(shì):
- 通過(guò)系統(tǒng)時(shí)間間隔來(lái)調(diào)用回調(diào)函數(shù),無(wú)需擔(dān)心系統(tǒng)負(fù)載和阻塞問(wèn)題,系統(tǒng)會(huì)自動(dòng)調(diào)整回調(diào)頻率。
- 由瀏覽器內(nèi)部進(jìn)行調(diào)度和優(yōu)化,性能更高,消耗的CPU和GPU資源更少。
- 避免幀丟失現(xiàn)象,確保回調(diào)連續(xù)執(zhí)行,實(shí)現(xiàn)更流暢的動(dòng)畫(huà)效果。
- 自動(dòng)合并多個(gè)回調(diào),避免不必要的開(kāi)銷(xiāo)。
- 與瀏覽器的刷新同步,不會(huì)在瀏覽器頁(yè)面不可見(jiàn)時(shí)執(zhí)行回調(diào)。
requestAnimationFrame的優(yōu)勢(shì)和適用場(chǎng)景
requestAnimationFrame
最適用于需要連續(xù)高頻執(zhí)行的動(dòng)畫(huà),如游戲開(kāi)發(fā),數(shù)據(jù)可視化動(dòng)畫(huà)等。它與瀏覽器刷新周期保持一致,不會(huì)因?yàn)殚g隔時(shí)間不均勻而導(dǎo)致動(dòng)畫(huà)卡頓。
使用方法
requestAnimationFrame的基本語(yǔ)法
requestAnimationFrame
接收一個(gè)回調(diào)函數(shù)作為參數(shù),該回調(diào)函數(shù)會(huì)在瀏覽器下一次重繪前執(zhí)行。
const animation = () => { // 執(zhí)行動(dòng)畫(huà) requestAnimationFrame(animation); } requestAnimationFrame(animation);
上面代碼會(huì)不停調(diào)用requestAnimationFrame
,在每次瀏覽器重繪前執(zhí)行回調(diào)函數(shù),實(shí)現(xiàn)連續(xù)動(dòng)畫(huà)效果。
如何在動(dòng)畫(huà)中使用requestAnimationFrame
可以在回調(diào)函數(shù)里更新動(dòng)畫(huà)的狀態(tài),然后清除上一幀,繪制新?tīng)顟B(tài)的這一幀:
let angle = 0; const render = () => { ctx.clearRect(0, 0, width, height); // 清除上一幀 // 更新?tīng)顟B(tài) angle += delta; ctx.beginPath(); ctx.arc(x, y, radius, 0, angle); ctx.stroke(); requestAnimationFrame(render); }
這樣通過(guò)在每次回調(diào)中更新參數(shù),就可以實(shí)現(xiàn)對(duì)象的連續(xù)動(dòng)畫(huà)了。
requestAnimationFrame的回調(diào)函數(shù)參數(shù)
requestAnimationFrame
的回調(diào)函數(shù)會(huì)收到一個(gè)參數(shù),這個(gè)參數(shù)是一個(gè)時(shí)間戳,單位為毫秒,代表requestAnimationFrame
被觸發(fā)的時(shí)間??梢愿鶕?jù)這個(gè)時(shí)間戳計(jì)算兩幀的時(shí)間間隔,來(lái)調(diào)整動(dòng)畫(huà)速度。
let prevTimestamp; const render = timestamp => { if (!prevTimestamp) prevTimestamp = timestamp; const delta = timestamp - prevTimestamp; // 根據(jù)時(shí)間間隔計(jì)算速度 x += speed * delta; prevTimestamp = timestamp; requestAnimationFrame(render); }
性能優(yōu)化
避免在requestAnimationFrame回調(diào)函數(shù)中進(jìn)行大量計(jì)算
由于requestAnimationFrame
的回調(diào)會(huì)在一個(gè)高優(yōu)先級(jí)的線程中被同步執(zhí)行,如果回調(diào)函數(shù)中有大量計(jì)算,會(huì)導(dǎo)致此線程被阻塞,從而引起頁(yè)面卡頓。
也就是如果 requestAnimationFrame
的回調(diào)函數(shù)執(zhí)行時(shí)間超過(guò)一幀(通常是 16.67 毫秒,因?yàn)闉g覽器通常以每秒約 60 幀的速度刷新),則可能會(huì)導(dǎo)致動(dòng)畫(huà)性能下降,可能出現(xiàn)掉幀的情況,最終影響用戶體驗(yàn)。這是因?yàn)闉g覽器每幀的時(shí)間是有限的,如果回調(diào)函數(shù)執(zhí)行時(shí)間過(guò)長(zhǎng),就會(huì)導(dǎo)致下一幀的準(zhǔn)備和繪制時(shí)間受到壓縮,導(dǎo)致動(dòng)畫(huà)卡頓。
通常,應(yīng)該盡量避免在 requestAnimationFrame
的回調(diào)函數(shù)中執(zhí)行耗時(shí)的操作。為了解決這個(gè)問(wèn)題,可以采取以下一些策略:
- 優(yōu)化回調(diào)函數(shù): 使回調(diào)函數(shù)盡可能簡(jiǎn)短,避免不必要的計(jì)算或循環(huán)。在回調(diào)中只執(zhí)行與動(dòng)畫(huà)相關(guān)的必要操作。
- 分幀處理: 如果動(dòng)畫(huà)需要處理大量數(shù)據(jù)或計(jì)算復(fù)雜的操作,可以將這些操作分散到多個(gè)
requestAnimationFrame
回調(diào)中,以避免長(zhǎng)時(shí)間的占用。 - Web Workers: 將耗時(shí)的計(jì)算放在獨(dú)立的 Web Worker 線程中執(zhí)行,以不影響主線程和動(dòng)畫(huà)渲染。
- 幀率控制: 如果回調(diào)函數(shù)耗時(shí)較長(zhǎng),可以根據(jù)回調(diào)函數(shù)的實(shí)際執(zhí)行時(shí)間來(lái)控制動(dòng)畫(huà)的幀率。減小動(dòng)畫(huà)對(duì)象的速度或者減少渲染質(zhì)量,以適應(yīng)當(dāng)前性能。
- 監(jiān)測(cè)性能: 使用瀏覽器開(kāi)發(fā)者工具來(lái)監(jiān)測(cè)性能,以找出哪些操作導(dǎo)致了回調(diào)函數(shù)執(zhí)行時(shí)間過(guò)長(zhǎng)。
總之,確保 requestAnimationFrame
回調(diào)函數(shù)的執(zhí)行時(shí)間盡量短,以確保動(dòng)畫(huà)的流暢性和性能。
使用硬件加速優(yōu)化動(dòng)畫(huà)性能
啟用GPU
加速渲染,可以顯著提升動(dòng)畫(huà)性能。
.animated { transform: translateZ(0); /* 開(kāi)啟硬件加速 */ }
如何在不同設(shè)備上實(shí)現(xiàn)平滑的動(dòng)畫(huà)效果
可根據(jù)requestAnimationFrame
回調(diào)的時(shí)間戳,計(jì)算這一幀與上一幀的間隔時(shí)間delta
,并根據(jù)delta
的值采取不同的優(yōu)化手段:
delta
特別小,說(shuō)明這一幀花費(fèi)時(shí)間過(guò)長(zhǎng),可能導(dǎo)致掉幀,可以降低動(dòng)畫(huà)對(duì)象的移動(dòng)速度或圖像質(zhì)量delta
逐漸變大,說(shuō)明動(dòng)畫(huà)逐漸卡頓,可以降低動(dòng)畫(huà)對(duì)象數(shù)量或復(fù)雜度delta
波動(dòng)較大,說(shuō)明系統(tǒng)資源不足,可以采用簡(jiǎn)單的動(dòng)畫(huà)作為降級(jí)策略
與其他動(dòng)畫(huà)庫(kù)的比較
requestAnimationFrame與setTimeout/setInterval的區(qū)別
setTimeout/setInterval
是固定時(shí)間間隔觸發(fā),requestAnimationFrame
依賴(lài)系統(tǒng)刷新調(diào)度setTimeout/setInterval
會(huì)無(wú)視頁(yè)面是否可見(jiàn),requestAnimationFrame
會(huì)停止刷新setTimeout/setInterval
難以避免丟幀問(wèn)題,requestAnimationFrame
與刷新同步避免丟幀
requestAnimationFrame與CSS動(dòng)畫(huà)的結(jié)合使用
requestAnimationFrame
可用于更新動(dòng)畫(huà)狀態(tài),實(shí)現(xiàn)復(fù)雜動(dòng)畫(huà)邏輯,而CSS動(dòng)畫(huà)用于聲明式定義動(dòng)畫(huà)的樣式變化,兩者可配合實(shí)現(xiàn)更豐富的動(dòng)畫(huà)效果。
實(shí)際應(yīng)用
使用requestAnimationFrame實(shí)現(xiàn)常見(jiàn)動(dòng)畫(huà)效果
可使用 requestAnimationFrame
實(shí)現(xiàn)對(duì)象軌跡動(dòng)畫(huà)、SVG圖形動(dòng)畫(huà)、加載動(dòng)畫(huà)等效果。
// 小球拖尾效果 const positions = []; const render = () => { // 添加新位置 positions.push({x, y}); if (positions.length > 10){ positions.shift(); } // 渲染小球 ctx.clearRect(0, 0, width, height); positions.forEach(pos => ctx.fillRect(pos.x, pos.y, 10, 10)); requestAnimationFrame(render); } // SVG繪制動(dòng)畫(huà) let length = 0; const render = () => { length += 4; svgLine.setAttribute("stroke-dasharray", length); if (length < 300) { requestAnimationFrame(render); } } requestAnimationFrame(render); // 進(jìn)度條加載動(dòng)畫(huà) let progress = 0; const render = () => { progress += 1; loadBar.style.width = progress + '%'; if(progress < 100) { requestAnimationFrame(render); } } requestAnimationFrame(render);
requestAnimationFrame在游戲開(kāi)發(fā)中的應(yīng)用
游戲需要非常流暢的畫(huà)面和實(shí)時(shí)的響應(yīng),這正是requestAnimationFrame
的優(yōu)勢(shì),它可用于實(shí)現(xiàn)游戲中的物體移動(dòng)、碰撞檢測(cè)、幀數(shù)控制等操作。
// 飛機(jī)射擊動(dòng)畫(huà) const update = () => { // 子彈位置更新 bullets.forEach(bullet => { bullet.position += speed; }) // 飛機(jī)位置更新 aircraft.position += delta * speed; // 繪制所有元素 render(bullets, aircraft); requestAnimationFrame(update); }
requestAnimationFrame在響應(yīng)式設(shè)計(jì)中的應(yīng)用
可使用requestAnimationFrame
來(lái)更平滑地執(zhí)行響應(yīng)式布局的變化,避免布局突然大幅移動(dòng)帶來(lái)的視覺(jué)沖擊感。
let width = 500; const resize = () => { width = container.clientWidth; box.style.width = width + "px"; requestAnimationFrame(resize); } window.addEventListener("resize", resize);
兼容性和后續(xù)發(fā)展
requestAnimationFrame的瀏覽器兼容性
requestAnimationFrame
現(xiàn)在已經(jīng)得到了廣泛支持,可以直接使用。對(duì)于不支持的瀏覽器,可以用setTimeout
模擬requestAnimationFrame
。
window.requestAnimationFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame || (cb => setTimeout(cb, 1000/60));
requestAnimationFrame的未來(lái)發(fā)展趨勢(shì)
未來(lái)requestAnimationFrame
可能會(huì)支持設(shè)置幀率、增強(qiáng)調(diào)度算法等,提升動(dòng)畫(huà)性能。Web工作者線程也可帶來(lái)更多優(yōu)化空間。
瀏覽器廠商也在繼續(xù)改進(jìn)相關(guān)API,比如setTimeout
和requestIdleCallback
也在朝著更精確的調(diào)度方向發(fā)展。
如何在不支持requestAnimationFrame的瀏覽器中實(shí)現(xiàn)類(lèi)似效果
可以通過(guò)自己實(shí)現(xiàn)一個(gè)polyfill
:
window.requestAnimationFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame || (cb => setTimeout(cb, 1000/60));
這樣就可以在大多數(shù)瀏覽器中使用 requestAnimationFrame
了。對(duì)于老舊瀏覽器,可以使用 setInterval
模擬,但效果會(huì)比較粗糙。
總結(jié)
requestAnimationFrame
是實(shí)現(xiàn)復(fù)雜動(dòng)畫(huà)的好幫手,必須掌握其用法與優(yōu)化技巧,才能發(fā)揮其最大效用。同時(shí)結(jié)合其他技術(shù)如CSS動(dòng)畫(huà)、Web Worker等也可以實(shí)現(xiàn)更好的性能。隨著瀏覽器的不斷進(jìn)步,requestAnimationFrame
還具有很大的拓展?jié)摿Α?/p>
以上就是requestAnimationFrame用法優(yōu)化源碼解析的詳細(xì)內(nèi)容,更多關(guān)于requestAnimationFrame優(yōu)化的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
vue 集成騰訊地圖實(shí)現(xiàn)api(附DEMO)
之前項(xiàng)目使用騰訊地圖,不利于開(kāi)發(fā)者查找,這篇文章主要介紹了vue 集成騰訊地圖實(shí)現(xiàn)api,具有一定的參考價(jià)值,感興趣的可以了解下2021-07-07JS實(shí)現(xiàn)仿騰訊微博無(wú)刷新刪除微博效果代碼
這篇文章主要介紹了JS實(shí)現(xiàn)仿騰訊微博無(wú)刷新刪除微博效果代碼,涉及JavaScript實(shí)現(xiàn)Ajax無(wú)刷新刪除的相關(guān)實(shí)現(xiàn)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-10-10詳解BootStrap表單驗(yàn)證中重置BootStrap-select驗(yàn)證提示不清除的坑
這篇文章主要介紹了詳解BootStrap表單驗(yàn)證中重置BootStrap-select驗(yàn)證提示不清除的坑,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-09-09JavaScript設(shè)置、獲取、清除單值和多值cookie的方法
cookie 是存儲(chǔ)于訪問(wèn)者的計(jì)算機(jī)中的變量。每當(dāng)同一臺(tái)計(jì)算機(jī)通過(guò)瀏覽器請(qǐng)求某個(gè)頁(yè)面時(shí),就會(huì)發(fā)送這個(gè) cookie。你可以使用 JavaScript 來(lái)創(chuàng)建和取回 cookie 的值,本文通過(guò)一段代碼給大家介紹js設(shè)置、獲取、清除單值和多值cookie的方法,需要的朋友一起學(xué)習(xí)吧2015-11-11javascript算法題 求任意一個(gè)1-9位不重復(fù)的N位數(shù)在該組合中的大小排列序號(hào)
從1--9中選取N個(gè)數(shù)字,組成不重復(fù)的N位數(shù),從小到大進(jìn)行編號(hào),當(dāng)輸入其中任何一個(gè)數(shù)M時(shí),能找出該數(shù)字對(duì)應(yīng)的編號(hào)2012-07-07Some tips of wmi scripting in jscript (1)
Some tips of wmi scripting in jscript (1)...2007-04-04JavaScript XML和string相互轉(zhuǎn)化實(shí)現(xiàn)代碼
兩個(gè)小function實(shí)現(xiàn)XML和string相互轉(zhuǎn)化,需要的朋友可以參考下。2011-07-07基于JavaScript繪制動(dòng)態(tài)花束的示例代碼
p5.js 是一個(gè)JavaScript的函數(shù)庫(kù),它在制作之初就和Processing有同樣的目標(biāo)。本文將利用p5.js 制作出一束動(dòng)態(tài)花束,感興趣的可以嘗試一下2022-06-06