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

Tz一號發表於2020-10-15

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

rAF

簡介

rAF是requestAnimationFrame的簡稱;

我們先從字面意思上理解requestAnimationFramerequest - 請求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方法制作動畫啦!

相關連結

結尾

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

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

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

相關文章