1. 程式人生 > >2020已經過去五分之四了,你確定還不來了解一下JS的rAF?

2020已經過去五分之四了,你確定還不來了解一下JS的rAF?

不會吧,不會吧,現在都2020年了不會還真人有人不知道JS的rAF吧???

rAF

簡介

rAF是requestAnimationFrame的簡稱;

我們先從字面意思上理解requestAnimationFrame,「request - 請求」,「Animation - 動畫」, 「Frame - 幀率;框架」,rAF難道是JS的動畫框架???,結果顯而易見並不是。但確實rAF和動畫有關係

我們先來看一下MDN官網對的requestAnimationFrame解釋:

window.requestAnimationFrame() 告訴瀏覽器——你希望執行一個動畫,並且要求瀏覽器在下次重繪之前呼叫指定的回撥函式更新動畫。該方法需要傳入一個回撥函式作為引數,該回調函式會在瀏覽器下一次重繪之前執行

瀏覽器相容性

requestAnimationFrame相容IE10及以上,這時候有人會有疑問,怎麼才到IE10啊,但其實我們最常使用的CSS3 animation屬性也是IE10之後才有的,在IE9之前想要實現動畫基本使用的是setTimeout/setInterval實現

作用及用法

requestAnimationFrame簡稱rAF,它是瀏覽器全域性物件window的一個方法。

相比於setTimeout的在固定時間後執行對應的動畫函式,rAF用於指示瀏覽器在下一次重新繪製螢幕影象時, 執行其提供的回撥函式。

這也是rAF的最大優勢–它能夠保證我們的動畫函式的每一次呼叫都對應著一次螢幕重繪,從而避免setTimeout

通過時間定義動畫頻率,與螢幕重新整理頻率不一致導致的丟幀。

詳細用法

requestAnimationFrame語法如下:


window.requestAnimationFrame(callback)

「引數;callback」 下一次重繪之前更新動畫幀所呼叫的函式(即上面所說的回撥函式)。該回調函式會被傳入DOMHighResTimeStamp引數,該引數與performance.now()的返回值相同,它表示requestAnimationFrame()開始去執行回撥函式的時刻。

「返回值」一個 long 整數,請求 ID ,是回撥列表中唯一的標識。是個非零值,沒別的意義。你可以傳這個值給 window.cancelAnimationFrame() 以取消回撥函式。

DOMHighResTimeStamp 指的是一個double型別,用於儲存毫秒級的時間值。這種型別可以用來描述離散的時間點或者一段時間(兩個離散時間點之間的時間差)。

performance.now()方法返回一個精確到毫秒的DOMHighResTimeStamp 。


它的實際常見用法類似於setTimeout,只是不需要設定時間間隔而已。如下:


const element = document.getElementById('some-element-you-want-to-animate'); 
let start;

function step(timestamp) {
  // timestamp回撥函式傳入的`DOMHighResTimeStamp`引數,也就是儲存毫秒級的時間值
  if (start === undefined)
    start = timestamp;
  const elapsed = timestamp - start;

  //這裡使用`Math.min()`確保元素剛好停在200px的位置。
  element.style.transform = 'translateX(' + Math.min(0.1 * elapsed, 200) + 'px)';

  if (elapsed < 2000) { // 在兩秒後停止動畫
    window.requestAnimationFrame(step);
  }
}

window.requestAnimationFrame(step);

上述程式碼的作用在每一次螢幕顯示影象的更新中,都將元素向左移動1px,停在200px位置上。

實際使用示例

「上才藝,E G M,E G M E G M E G M」

我們以在3000毫秒內移動1500px距離的動畫為例

setTimeout的實現方式

以下程式碼通過setTimeout每10毫秒為間隔時間改變一次元素的位置以實現元素的動畫效果, 當然,可以通過改變這個間隔時間來微調動畫效果,可是你永遠沒有辦法確定最優方案,因為它總會和重新整理頻率存在交叉。


<div id="div" style="width:100px; height:100px; background-color:#000; position: absolute;left:0; top:0;">
    
</div>

<script type="text/javascript">
let divEle = document.getElementById("div");

const distance = 1500; // 需要移動的距離
const timeCount = 3000; // 需要使用的時間

const intervalTime = 10; // 設定間隔時間為10ms
let runCount = timeCount / intervalTime; // 相除得到執行次數
let moveValue = distance / runCount; // 每次執行移動的距離

function handler() {
    let left = parseInt(divEle.style.left);
    if(left >= distance) {
      // 當距離左側的距離超出需要移動的距離停止
        return;
    }
    divEle.style.left = left + moveValue;
    window.setTimeout(handler, intervalTime);
}

window.setTimeout(handler, intervalTime);
</script>

requestAnimationFrame的實現方式

「從setTimeout切換到 requestAnimationFrame很容易,因為它們都安排了一個回撥。對於連續動畫,在呼叫動畫函式之後再次呼叫requestAnimationFrame。」

request 會把每一幀中的所有DOM操作集中起來,在一次重繪或迴流中就完成(這點很像虛擬DOM不是~),並且重繪或迴流的時間間隔緊緊跟隨瀏覽器的重新整理頻率,這樣就不會出現過度渲染的問題,保證了流暢的需求以及瀏覽器的完美渲染。


<div id="div" style="width:100px; height:100px; background-color:#000; position: absolute;left:0; top:0;">
    
</div>

<script type="text/javascript">

let divEle = document.getElementById("div");

const distance = 1500; // 需要移動的距離
const timeCount = 3000; // 需要使用的時間

function handler( time ) {
    // time為rAF返回的毫秒級時間單位,當time的大於timeCount的值則停止
    // time理論上是從 1 開始到timeCount定義的3000,
    if(time > timeCount) {
        time = timeCount;
    }
    // 這句程式碼的作用是 time理論上是從 1 至 3000
    // 當到達3000的時候,time * distance / timeCount得到的一定是distance的值1500
    divEle.style.left = time * distance / timeCount;
    window.requestAnimationFrame( handler ); // 迴圈呼叫,渲染完成會停止
}

 window.requestAnimationFrame( handler );
</script>
    

requestAnimationFrame的優點

為什麼不使用settimeout?

setTimeout通過設定一個時間間隔來不斷的更新螢幕影象,從而完成動圖。 它的優點是可控性高,可以進行編碼式的動畫效果實現。

setTimeout缺點:

  1. 「造成無用的函式執行開銷:」

也就是過度繪製,同時因為更新影象的頻率和螢幕的重新整理重繪製步調不一致,會產生丟幀,在低效能的顯示器動畫看起來就會卡頓。

  1. 「當網頁標籤或瀏覽器置於後臺不可見時,仍然會執行,造成資源浪費」

  2. 「API本身達不到毫秒級的精確:」

如果使用 setTimeout或者setInterval 那麼需要我們制定時間 假設給予 (1000/60)理論上就可以完成60幀速率的動畫。所以事實是瀏覽器可以“強制規定時間間隔的下限(clamping th timeout interval)”,一般瀏覽器所允許的時間再5-10毫秒,也就是說即使你給了某個小於10的數,可能也要等待10毫秒。

  1. 「瀏覽器不能完美執行:」

當動畫使用10ms的settimeout繪製動畫時,您將看到一個時序不匹配,如下所示。

我們的顯示屏一般是「16.7ms(即60FPS)的顯示頻率」,上圖的第一行代表大多數監視器上顯示的「16.7ms顯示頻率」,上圖的第二行代表「10ms的典型setTimeout」。由於在顯示重新整理間隔之前發生了另一個繪製請求,因此無法繪製每次的第三個繪製(紅色箭頭指示)。這種透支會導致動畫斷斷續續,「因為每三幀都會丟失」。計時器解析度的降低也會對電池壽命產生負面影響,並降低其他應用程式的效能。

如果使用requestAnimationFrame可以解決setTimeout的丟幀問題,因為它使應用程式時通知(且僅當)的瀏覽器需要更新頁面顯示,渲染時間由系統處理。因此,應用程式與瀏覽器繪畫間隔完全一致,並且僅使用適當數量的資源。

requestAnimationFrame的好處

相比於setTimeout的在固定時間後執行對應的動畫函式,requestAnimationFrame用於指示瀏覽器在下一次重新繪製螢幕影象時, 執行其提供的回撥函式。

  • 「使瀏覽器畫面的重繪和迴流與顯示器的重新整理頻率同步」它能夠保證我們的動畫函式的每一次呼叫都對應著一次螢幕重繪,從而避免setTimeout通過時間定義動畫頻率,與螢幕重新整理頻率不一致導致的丟幀。

  • 「節省系統資源,提高效能和視覺效果」在頁面被置於後臺或隱藏時,會自動的停止,不進行函式的執行,當頁面啟用時,會重新從上次停止的狀態開始執行,因此在效能開銷上也會相比setTimeout小很多。

相容問題

目前的時間點上,幾乎所有的瀏覽器現行版本都支援了requestAnimationFrame函式。但在一部分瀏覽器上還需要加上相容性字首。 下面這是比較全面的方法用來使requestAnimation相容各瀏覽器:


(function() {
    var lastTime = 0;
    var vendors = ['webkit', 'moz']; // 瀏覽器字首
    // 當window.requestAnimationFrame不存在時執行for迴圈,新增字首
    for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
        window.requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame'];
        window.cancelAnimationFrame = window[vendors[x] + 'CancelAnimationFrame'] ||
                                      window[vendors[x] + 'CancelRequestAnimationFrame'];
    }

    //當新增字首後依舊不存在,則使用setTimeout替代
    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方法制作動畫啦!

相關連結

  • 張鑫旭 - requestAnimationFrame

  • MDN - requestAnimationFrame

  • Microsoft - requestAnimationFrame

  • 個人網站:zhaohongcheng.com

  • GitHub:https://github.com/Tzlibai

結尾

如有疑問,可在下方留言,會第一時間進行回覆

謝謝你願意花時間閱讀這篇文章,希望可以對你有所幫助!

我曾踏足山巔,也曾跌落谷底,兩者都讓我受益良多。