不會吧,不會吧,現在都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缺點:
「造成無用的函式執行開銷:」
也就是過度繪製,同時因為更新影像的頻率和螢幕的重新整理重繪製步調不一致,會產生丟幀,在低效能的顯示器動畫看起來就會卡頓。
「當網頁標籤或瀏覽器置於後臺不可見時,仍然會執行,造成資源浪費」
「API本身達不到毫秒級的精確:」
如果使用 setTimeout或者setInterval 那麼需要我們制定時間 假設給予 (1000/60)理論上就可以完成60幀速率的動畫。所以事實是瀏覽器可以“強制規定時間間隔的下限(clamping th timeout interval)”,一般瀏覽器所允許的時間再5-10毫秒,也就是說即使你給了某個小於10的數,可能也要等待10毫秒。
「瀏覽器不能完美執行:」
當動畫使用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方法制作動畫啦!
相關連結
個人網站:zhaohongcheng.com
GitHub:https://github.com/Tzlibai
結尾
如有疑問,可在下方留言,會第一時間進行回覆
謝謝你願意花時間閱讀這篇文章,希望可以對你有所幫助!
我曾踏足山巔,也曾跌落谷底,兩者都讓我受益良多。