国产成人精品久久免费动漫-国产成人精品天堂-国产成人精品区在线观看-国产成人精品日本-a级毛片无码免费真人-a级毛片毛片免费观看久潮喷

您的位置:首頁技術(shù)文章
文章詳情頁

requestAnimationFrame定時(shí)動(dòng)畫屏幕刷新率節(jié)流示例淺析

瀏覽:56日期:2022-06-02 09:26:47
目錄
  • 前言
  • 早期定時(shí)動(dòng)畫
  • 屏幕刷新率
  • requestAnimationFrame
  • cancelAnimationFrame
  • 通過 requestAnimationFrame 節(jié)流

前言

很長時(shí)間以來,計(jì)時(shí)器和定時(shí)執(zhí)行都是 JavaScript 動(dòng)畫最先進(jìn)的工具。雖然 CSS 過渡和動(dòng)畫方便了開發(fā)者實(shí)現(xiàn)某些動(dòng)畫,但 JavaScript 動(dòng)畫領(lǐng)域多年來進(jìn)展甚微。requestAnimationFrame() 方法應(yīng)運(yùn)而生,這個(gè)方法會(huì)告訴瀏覽器要執(zhí)行動(dòng)畫了,于是瀏覽器可以通過最優(yōu)方式確定重繪的時(shí)序。

早期定時(shí)動(dòng)畫

以前,在 JavaScript 中創(chuàng)建動(dòng)畫基本上就是使用 setInterval() 來控制動(dòng)畫的執(zhí)行:

(function () {
  function updateAnimations() {
    doAnimation1();
    doAnimation2();
    // ...
  }
  setInterval(updateAnimations, 100);
})();

這種定時(shí)動(dòng)畫的問題在于,無法準(zhǔn)確知曉循環(huán)之間的延時(shí)。

無論是 setInterval() 還是 setTimeout(),都是不能保證時(shí)間精度的。作為第二個(gè)參數(shù)的延時(shí)只能保證何時(shí)會(huì)把代碼添加到瀏覽器的任務(wù)隊(duì)列,并不能保證添加到隊(duì)列就會(huì)立即執(zhí)行。如果隊(duì)列前面還有其他任務(wù),那么就要等這些任務(wù)執(zhí)行完再執(zhí)行。

簡單來講,這里的毫秒延時(shí)不是指何時(shí)這些代碼會(huì)執(zhí)行,而是指到時(shí)候會(huì)把回調(diào)添加到任務(wù)隊(duì)列。如果添加到隊(duì)列后,主線程還被其他任務(wù)占用,那么回調(diào)就不會(huì)立即執(zhí)行。

知道何時(shí)繪制下一幀是創(chuàng)造平滑動(dòng)畫的關(guān)鍵,所以 setInterval()setTimeout() 的不精確是個(gè)大問題。

瀏覽器自身計(jì)時(shí)器的精度讓這個(gè)問題雪上加霜。瀏覽器計(jì)時(shí)器的精度不足毫秒,最厲害的 Chrome 計(jì)時(shí)器精度為 4ms。更麻煩的是,瀏覽器又開始對(duì)切換到后臺(tái)或不活躍的標(biāo)簽頁中的計(jì)時(shí)器執(zhí)行限流,因此即使將時(shí)間間隔設(shè)為最優(yōu),也免不了只能得到近似的結(jié)果。

屏幕刷新率

一般計(jì)算機(jī)顯示器的屏幕刷新率都是 60HZ,基本上意味著每秒需要重繪 60 次。大多數(shù)瀏覽器會(huì)限制重繪頻率,使其不超過屏幕的刷新率,因?yàn)槌^屏幕刷新率用戶也感知不到。

所以,實(shí)現(xiàn)平滑動(dòng)畫最佳的重繪時(shí)間間隔為 1000ms/60,大約 17 ms。以這個(gè)速度重繪可以實(shí)現(xiàn)最平滑的動(dòng)畫,因?yàn)檫@已經(jīng)是瀏覽器的極限了。

requestAnimationFrame

requestAnimationFrame() 這個(gè)方法可以通知瀏覽器某些 JavaScript 代碼要執(zhí)行動(dòng)畫了,這樣瀏覽器就可以在運(yùn)行某些代碼后進(jìn)行適當(dāng)?shù)膬?yōu)化。

requestAnimationFrame() 這個(gè)方法接收一個(gè)參數(shù),該參數(shù)是一個(gè)要在重繪屏幕前調(diào)用的函數(shù)。這個(gè)函數(shù)就是修改 DOM 樣式以反映下一次重繪有什么變化的地方。為了實(shí)現(xiàn)動(dòng)畫循環(huán),可以把多個(gè) requestAnimationFrame() 調(diào)用串聯(lián)起來,就像以前使用 setTimeout() 一樣:

function updateProgress() {
  var div = document.getElementById("status");
  div.style.width = parseInt(div.style.width, 10) + 5 + "%";
  if (div.style.left != "100%") {
    requestAnimationFrame(updateProgress);
  }
}
requestAnimationFrame(updateProgress);

因?yàn)?requestAnimationFrame() 只會(huì)調(diào)用一次傳入的函數(shù),所以每次更新用戶界面時(shí)需要再手動(dòng)調(diào)用它一次。同時(shí),也需要控制動(dòng)畫何時(shí)停止。結(jié)果就會(huì)得到非常平滑的動(dòng)畫。

requestAnimationFrame() 已經(jīng)解決了瀏覽器不知道 JavaScript 動(dòng)畫何時(shí)開始的問題,以及最佳間隔時(shí)間是多少的問題。但是,如果我們想知道自己的代碼實(shí)際的執(zhí)行時(shí)間呢?同樣有解決方案。

傳給 requestAnimationFrame() 的函數(shù)實(shí)際上可以接收一個(gè)參數(shù),該參數(shù)表示下次重繪的時(shí)間。這一點(diǎn)非常重要:requestAnimationFrame() 實(shí)際上把重繪任務(wù)安排在了未來一個(gè)已知的時(shí)間點(diǎn)上,而且通過這個(gè)參數(shù)告訴了開發(fā)者,那么基于這個(gè)參數(shù),就可以更好地決定如何調(diào)優(yōu)動(dòng)畫了:

function foo(t) {
  console.log(t);
  requestAnimationFrame(foo);
}
requestAnimationFrame(foo);

cancelAnimationFrame

const requestID = window.requestAnimationFrame((t) => {
  console.log(t);
});
window.cancelAnimationFrame(requestID);

通過 requestAnimationFrame 節(jié)流

支持這個(gè)方法的瀏覽器實(shí)際上會(huì)暴露出作為鉤子的回調(diào)隊(duì)列。所謂鉤子,就是瀏覽器在執(zhí)行下一次重繪之前的一個(gè)點(diǎn)。這個(gè)回調(diào)隊(duì)列是一個(gè)可修改的函數(shù)列表,包含應(yīng)該在重繪之前調(diào)用的函數(shù)。每次調(diào)用 requestAnimationFrame() 都會(huì)在隊(duì)列上推入一個(gè)回調(diào)函數(shù),隊(duì)列的長度沒有限制。

這個(gè)回調(diào)隊(duì)列的行為不一定跟動(dòng)畫有關(guān)。通過 requestAnimationFrame() 遞歸地向隊(duì)列中加入回調(diào)函數(shù),可以保證每次重繪最多只調(diào)用一次回調(diào)函數(shù),這是一個(gè)非常好的節(jié)流工具。在頻繁執(zhí)行影響頁面外觀的代碼時(shí)(比如滾動(dòng)事件監(jiān)聽器),可以利用這個(gè)回調(diào)隊(duì)列進(jìn)行節(jié)流。

先看一個(gè)原生實(shí)現(xiàn),其中的滾動(dòng)事件監(jiān)聽器每次觸發(fā)都會(huì)調(diào)用名為 expensiveOperation()(耗時(shí)操作) 的函數(shù)。當(dāng)向下滾動(dòng)網(wǎng)頁時(shí),這個(gè)事件很快就會(huì)被觸發(fā)并執(zhí)行成百上千次:

function expensiveOperation() {
  console.log("Invoked at", Date.now());
}
window.addEventListener("scroll", () => {
  expensiveOperation();
});

如果想把事件處理程序的調(diào)用限制在每次重繪之前,那么就可以把它封裝到 requestAnimationFrame() 調(diào)用中:

function expensiveOperation() {
  console.log("Invoked at", Date.now());
}
window.addEventListener("scroll", () => {
  window.requestAnimationFrame(expensiveOperation);
});

這樣會(huì)把所有回調(diào)的執(zhí)行集中在重繪鉤子,但不會(huì)過濾掉每次重繪的多余調(diào)用。我們可以定義一個(gè)標(biāo)志變量,在回調(diào)中設(shè)置其狀態(tài),就能將多余的調(diào)用屏蔽:

let enqueued = false;
function expensiveOperation() {
  console.log("Invoked at", Date.now());
  enqueued = false;
}
window.addEventListener("scroll", () => {
  if (!enqueued) {
    enqueued = true;
    window.requestAnimationFrame(expensiveOperation);
  }
});

因?yàn)橹乩L是非常頻繁的操作,所以這算不上是真正的節(jié)流。更好的方法是配合使用一個(gè)計(jì)時(shí)器來限制操作執(zhí)行的頻率。這樣,計(jì)時(shí)器可以限制實(shí)際的操作執(zhí)行間隔,而 requestAnimationFrame() 控制在瀏覽器的哪個(gè)渲染周期中執(zhí)行:

let enabled = true;
function expensiveOperation() {
  console.log("Invoked at", Date.now());
}
window.addEventListener("scroll", () => {
  if (enabled) {
    enqueued = false;
    window.requestAnimationFrame(expensiveOperation);
    window.setTimeout(() => (enabled = true), 50);
  }
});

上面的例子將回調(diào)限制為大約 50ms 執(zhí)行一次。

以上就是requestAnimationFrame定時(shí)動(dòng)畫屏幕刷新率節(jié)流示例淺析的詳細(xì)內(nèi)容,更多關(guān)于requestAnimationFrame刷新節(jié)流的資料請(qǐng)關(guān)注其它相關(guān)文章!

標(biāo)簽: JavaScript
主站蜘蛛池模板: 日本精品一区二区三区在线 | 国产一区二区在线 |播放 | 怡红院最新免费全部视频 | 精品国产成人系列 | 99r精品视频 | 一区二区三区 日韩 | 在线观看片成人免费视频 | 国产亚洲精品自在线观看 | 国产成人无精品久久久久国语 | 亚洲精品成人一区二区www | 99久久精品视香蕉蕉er热资源 | 国产成人免费高清在线观看 | 日韩中文字幕视频在线 | 国产成人久久精品激情91 | 国产精品久久久精品三级 | 欧美特级午夜一区二区三区 | 国产成人免费午夜在线观看 | 在线播放精品一区二区啪视频 | 另类自拍 | 亚洲一区 在线播放 | 国产成人18黄网站免费网站 | 国产欧美自拍视频 | 亚洲成aⅴ人在线观看 | avav男人天堂 | 亚洲视频手机在线 | 国产亚洲欧美久久精品 | 最新国产精品视频免费看 | 韩国激情啪啪 | 7777在线视频 | 久久丁香| 怡红院在线观看视频 | 欧美一级在线毛片免费观看 | 综合久 | 精品久久久久久久高清 | 综合在线视频 | 亚洲精品亚洲人成毛片不卡 | 老司机午夜在线视频免费观 | 久夜色精品国产一区二区三区 | 一区二区三区不卡视频 | 国产性自爱拍偷在在线播放 | 美女动作一级毛片 |