requestAnimationFrame用法優(yōu)化源碼解析
介紹
什么是requestAnimationFrame
requestAnimationFrame是瀏覽器用于定時(shí)循環(huán)操作的一個(gè)API,通常用于動(dòng)畫和游戲開發(fā)。它會(huì)把每一幀中的所有DOM操作集中起來,在重繪之前一次性更新,并且關(guān)聯(lián)到瀏覽器的重繪操作。
為什么使用requestAnimationFrame而不是setTimeout或setInterval
與setTimeout或setInterval相比,requestAnimationFrame具有以下優(yōu)勢(shì):
- 通過系統(tǒng)時(shí)間間隔來調(diào)用回調(diào)函數(shù),無需擔(dān)心系統(tǒng)負(fù)載和阻塞問題,系統(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)畫效果。
- 自動(dòng)合并多個(gè)回調(diào),避免不必要的開銷。
- 與瀏覽器的刷新同步,不會(huì)在瀏覽器頁面不可見時(shí)執(zhí)行回調(diào)。
requestAnimationFrame的優(yōu)勢(shì)和適用場(chǎng)景
requestAnimationFrame最適用于需要連續(xù)高頻執(zhí)行的動(dòng)畫,如游戲開發(fā),數(shù)據(jù)可視化動(dòng)畫等。它與瀏覽器刷新周期保持一致,不會(huì)因?yàn)殚g隔時(shí)間不均勻而導(dǎo)致動(dòng)畫卡頓。
使用方法
requestAnimationFrame的基本語法
requestAnimationFrame接收一個(gè)回調(diào)函數(shù)作為參數(shù),該回調(diào)函數(shù)會(huì)在瀏覽器下一次重繪前執(zhí)行。
const animation = () => {
// 執(zhí)行動(dòng)畫
requestAnimationFrame(animation);
}
requestAnimationFrame(animation);上面代碼會(huì)不停調(diào)用requestAnimationFrame,在每次瀏覽器重繪前執(zhí)行回調(diào)函數(shù),實(shí)現(xiàn)連續(xù)動(dòng)畫效果。
如何在動(dòng)畫中使用requestAnimationFrame
可以在回調(diào)函數(shù)里更新動(dòng)畫的狀態(tài),然后清除上一幀,繪制新狀態(tài)的這一幀:
let angle = 0;
const render = () => {
ctx.clearRect(0, 0, width, height); // 清除上一幀
// 更新狀態(tài)
angle += delta;
ctx.beginPath();
ctx.arc(x, y, radius, 0, angle);
ctx.stroke();
requestAnimationFrame(render);
}這樣通過在每次回調(diào)中更新參數(shù),就可以實(shí)現(xiàn)對(duì)象的連續(xù)動(dòng)畫了。
requestAnimationFrame的回調(diào)函數(shù)參數(shù)
requestAnimationFrame的回調(diào)函數(shù)會(huì)收到一個(gè)參數(shù),這個(gè)參數(shù)是一個(gè)時(shí)間戳,單位為毫秒,代表requestAnimationFrame被觸發(fā)的時(shí)間??梢愿鶕?jù)這個(gè)時(shí)間戳計(jì)算兩幀的時(shí)間間隔,來調(diào)整動(dòng)畫速度。
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)致此線程被阻塞,從而引起頁面卡頓。
也就是如果 requestAnimationFrame 的回調(diào)函數(shù)執(zhí)行時(shí)間超過一幀(通常是 16.67 毫秒,因?yàn)闉g覽器通常以每秒約 60 幀的速度刷新),則可能會(huì)導(dǎo)致動(dòng)畫性能下降,可能出現(xiàn)掉幀的情況,最終影響用戶體驗(yàn)。這是因?yàn)闉g覽器每幀的時(shí)間是有限的,如果回調(diào)函數(shù)執(zhí)行時(shí)間過長(zhǎng),就會(huì)導(dǎo)致下一幀的準(zhǔn)備和繪制時(shí)間受到壓縮,導(dǎo)致動(dòng)畫卡頓。
通常,應(yīng)該盡量避免在 requestAnimationFrame 的回調(diào)函數(shù)中執(zhí)行耗時(shí)的操作。為了解決這個(gè)問題,可以采取以下一些策略:
- 優(yōu)化回調(diào)函數(shù): 使回調(diào)函數(shù)盡可能簡(jiǎn)短,避免不必要的計(jì)算或循環(huán)。在回調(diào)中只執(zhí)行與動(dòng)畫相關(guān)的必要操作。
- 分幀處理: 如果動(dòng)畫需要處理大量數(shù)據(jù)或計(jì)算復(fù)雜的操作,可以將這些操作分散到多個(gè)
requestAnimationFrame回調(diào)中,以避免長(zhǎng)時(shí)間的占用。 - Web Workers: 將耗時(shí)的計(jì)算放在獨(dú)立的 Web Worker 線程中執(zhí)行,以不影響主線程和動(dòng)畫渲染。
- 幀率控制: 如果回調(diào)函數(shù)耗時(shí)較長(zhǎng),可以根據(jù)回調(diào)函數(shù)的實(shí)際執(zhí)行時(shí)間來控制動(dòng)畫的幀率。減小動(dòng)畫對(duì)象的速度或者減少渲染質(zhì)量,以適應(yīng)當(dāng)前性能。
- 監(jiān)測(cè)性能: 使用瀏覽器開發(fā)者工具來監(jiān)測(cè)性能,以找出哪些操作導(dǎo)致了回調(diào)函數(shù)執(zhí)行時(shí)間過長(zhǎng)。
總之,確保 requestAnimationFrame 回調(diào)函數(shù)的執(zhí)行時(shí)間盡量短,以確保動(dòng)畫的流暢性和性能。
使用硬件加速優(yōu)化動(dòng)畫性能
啟用GPU加速渲染,可以顯著提升動(dòng)畫性能。
.animated {
transform: translateZ(0); /* 開啟硬件加速 */
}如何在不同設(shè)備上實(shí)現(xiàn)平滑的動(dòng)畫效果
可根據(jù)requestAnimationFrame回調(diào)的時(shí)間戳,計(jì)算這一幀與上一幀的間隔時(shí)間delta,并根據(jù)delta的值采取不同的優(yōu)化手段:
delta特別小,說明這一幀花費(fèi)時(shí)間過長(zhǎng),可能導(dǎo)致掉幀,可以降低動(dòng)畫對(duì)象的移動(dòng)速度或圖像質(zhì)量delta逐漸變大,說明動(dòng)畫逐漸卡頓,可以降低動(dòng)畫對(duì)象數(shù)量或復(fù)雜度delta波動(dòng)較大,說明系統(tǒng)資源不足,可以采用簡(jiǎn)單的動(dòng)畫作為降級(jí)策略
與其他動(dòng)畫庫的比較
requestAnimationFrame與setTimeout/setInterval的區(qū)別
setTimeout/setInterval是固定時(shí)間間隔觸發(fā),requestAnimationFrame依賴系統(tǒng)刷新調(diào)度setTimeout/setInterval會(huì)無視頁面是否可見,requestAnimationFrame會(huì)停止刷新setTimeout/setInterval難以避免丟幀問題,requestAnimationFrame與刷新同步避免丟幀
requestAnimationFrame與CSS動(dòng)畫的結(jié)合使用
requestAnimationFrame可用于更新動(dòng)畫狀態(tài),實(shí)現(xiàn)復(fù)雜動(dòng)畫邏輯,而CSS動(dòng)畫用于聲明式定義動(dòng)畫的樣式變化,兩者可配合實(shí)現(xiàn)更豐富的動(dòng)畫效果。
實(shí)際應(yīng)用
使用requestAnimationFrame實(shí)現(xiàn)常見動(dòng)畫效果
可使用 requestAnimationFrame 實(shí)現(xiàn)對(duì)象軌跡動(dòng)畫、SVG圖形動(dòng)畫、加載動(dòng)畫等效果。
// 小球拖尾效果
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)畫
let length = 0;
const render = () => {
length += 4;
svgLine.setAttribute("stroke-dasharray", length);
if (length < 300) {
requestAnimationFrame(render);
}
}
requestAnimationFrame(render);
// 進(jìn)度條加載動(dòng)畫
let progress = 0;
const render = () => {
progress += 1;
loadBar.style.width = progress + '%';
if(progress < 100) {
requestAnimationFrame(render);
}
}
requestAnimationFrame(render);requestAnimationFrame在游戲開發(fā)中的應(yīng)用
游戲需要非常流暢的畫面和實(shí)時(shí)的響應(yīng),這正是requestAnimationFrame的優(yōu)勢(shì),它可用于實(shí)現(xiàn)游戲中的物體移動(dòng)、碰撞檢測(cè)、幀數(shù)控制等操作。
// 飛機(jī)射擊動(dòng)畫
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來更平滑地執(zhí)行響應(yīng)式布局的變化,避免布局突然大幅移動(dòng)帶來的視覺沖擊感。
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的未來發(fā)展趨勢(shì)
未來requestAnimationFrame可能會(huì)支持設(shè)置幀率、增強(qiáng)調(diào)度算法等,提升動(dòng)畫性能。Web工作者線程也可帶來更多優(yōu)化空間。
瀏覽器廠商也在繼續(xù)改進(jìn)相關(guān)API,比如setTimeout和requestIdleCallback也在朝著更精確的調(diào)度方向發(fā)展。
如何在不支持requestAnimationFrame的瀏覽器中實(shí)現(xiàn)類似效果
可以通過自己實(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)畫的好幫手,必須掌握其用法與優(yōu)化技巧,才能發(fā)揮其最大效用。同時(shí)結(jié)合其他技術(shù)如CSS動(dòng)畫、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)文章
JavaScript開發(fā)的七個(gè)實(shí)用小技巧(很有用)
日常開發(fā)中,我們經(jīng)常需要編寫大量的js代碼,下面這篇文章主要給大家介紹了關(guān)于JavaScript開發(fā)的七個(gè)實(shí)用小技巧,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-04-04
JavaScript中判斷網(wǎng)絡(luò)狀態(tài)的幾種方法
這篇文章主要介紹了如何使用JavaScript的`navigator.onLine`屬性和`ononline`、`onoffline`事件來檢測(cè)瀏覽器的在線狀態(tài),并提供了兼容性代碼示例,需要的朋友可以參考下2025-02-02
JS簡(jiǎn)單生成隨機(jī)數(shù)(隨機(jī)密碼)的方法
這篇文章主要介紹了JS簡(jiǎn)單生成隨機(jī)數(shù)(隨機(jī)密碼)的方法,簡(jiǎn)單分析了javascript隨機(jī)數(shù)相關(guān)函數(shù)并結(jié)合具體實(shí)例形式分析了隨機(jī)數(shù)的相關(guān)生成技巧,需要的朋友可以參考下2017-05-05
ECharts數(shù)據(jù)可視化基本使用之常用圖表類型
這篇文章主要給大家介紹了關(guān)于ECharts數(shù)據(jù)可視化基本使用之常用圖表類型的相關(guān)資料,echarts是一款基于JavaScript的數(shù)據(jù)可視化圖表庫,提供直觀,生動(dòng),可交互,可個(gè)性化定制的數(shù)據(jù)可視化圖表,需要的朋友可以參考下2023-11-11
詳解CocosCreator系統(tǒng)事件是怎么產(chǎn)生及觸發(fā)的
這篇文章主要介紹了CocosCreator系統(tǒng)事件是怎么產(chǎn)生及觸發(fā)的,雖然內(nèi)容不少,但是只要一點(diǎn)點(diǎn)抽絲剝繭,具體分析其內(nèi)容,就會(huì)豁然開朗2021-04-04
僅IE9/10同時(shí)支持script元素的onload和onreadystatechange事件分析
測(cè)試結(jié)果可以看出,IE9后已經(jīng)開始支持script的onload事件了。一直以來我們判斷js文件是否已經(jīng)加載完成就是用以上的兩個(gè)事件。2011-04-04
javascript原型鏈學(xué)習(xí)記錄之繼承實(shí)現(xiàn)方式分析
這篇文章主要介紹了javascript原型鏈學(xué)習(xí)記錄之繼承實(shí)現(xiàn)方式,結(jié)合實(shí)例形式分析了javascript使用原型鏈實(shí)現(xiàn)繼承的相關(guān)操作技巧與注意事項(xiàng),需要的朋友可以參考下2019-05-05

