最近工作中經常遇到動畫的情況,之前常用的方法使用setTimeout或setInterval實現,但隨着應用的越來越復雜,性能方面就會降低。所以選擇使用requestAnimationFrame來實現相同效果。本文簡單記錄使用rAF的方法。
requestAnimationFrame是HTML5中提供的動畫API,簡稱rAF,即請求動畫幀。可以讓瀏覽器優化並行的動畫動作,更合理的重新排列動作序列,並把能夠合並的動作放在一個渲染周期內完成,從而呈現出更流暢的動畫效果。說rAF之前先來簡單了解與之相關的幾個概念。
屏幕刷新頻率
屏幕的刷新頻率可在電腦中“高級顯示設置”中查看,一般為60Hz,就是說,屏幕靜置情況下,顯示器會以每秒60次的頻率不斷更新屏幕上的圖像,因為人的“視覺停留效應”,並感覺不到變化或者抖動,看到的仍是一幅幅連續的畫面,其實這中間間隔時間是16.7ms(即1000/60)。
我們知道動畫的本質就是讓人看到圖像在連貫、平滑地變動,那根據上面我們知道的,16.7ms屏幕刷新一次,在刷新時讓圖像移動1px,這樣在視覺效果上便形成了動畫。
setTimeout
從上面的介紹,我們可以了解,setTimeout其實就是通過一個時間間隔來不斷更新圖像形成的動畫效果。但setTimeout在某些機型或復雜應用中會出現卡頓現象,也就是常說的“丟幀”。這事因為setTimeout只能設置固定的時間間隔,而不同的屏幕、機型會有不同的分辨率,而且setTimeout任務是被放進異步隊列中的,所以實際執行時間會比設定時間晚一點。這些原因就導致了setTimeout動畫的卡頓現象。
requestAnimationFrame
知道了setTimeout的缺點,rAF的出現就順理成章了。rAF的回調函數執行時機由系統決定,也就是說,系統每次繪制前都會主動調用rAF中的回調函數,如果系統繪制頻率是60Hz,那回調函數就是16.7ms被執行一次,如果系統繪制頻率是75Hz,那么這個時間間隔就是1000/75=13.3ms,這樣就保證回調函數在每次繪制中間都能執行一次,就不會出現丟幀的現象,也不會有卡頓的問題。
這個API的使用方法也很簡單,如下:
var progress = 0;
//回調函數
function render() {
progress += 1; //修改圖像的位置
if (progress < 100) {
//在動畫沒有結束前,遞歸渲染
window.requestAnimationFrame(render);
}
}
//第一幀渲染
window.requestAnimationFrame(render);
優雅降級
因為rAF是HTML5提供的新API,目前還存在兼容性問題,所以需要對其進行優雅降級處理,這里提供網上的一些比較成熟的解決方案:
;(function () {
var lastTime = 0
var vendors = ['ms', 'moz', 'webkit', 'o']
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']
}
if (!window.requestAnimationFrame)
window.requestAnimationFrame = function (callback, element) {
var currTime = new Date().getTime()
var timeToCall = Math.max(0, 16 - (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)
}
})()
以上來自tweenjs提供的RequestAnimationFrame.js https://github.com/tweenjs/tween.js/blob/master/examples/js/RequestAnimationFrame.js
實例
下面通過一個簡單的例子來演示rAF的用法。
在html內設置一個圓,然后讓其動起來。
<!DOCTYPE html>
<html>
<head>
<title>rAF</title>
<style>
* {margin: 0;padding: 0}
.box {width: 100px;height: 100px;border-radius: 100%;background: #f00;position: absolute;left: 0;top: 0}
</style>
</head>
<body>
<div class="box" id="box"></div>
</body>
</html>
簡單的方式如下:
var box = document.getElementById('box')
var flag = false
var left = 0
function render() {
if (flag) {
if (left >= 100) {
flag = false
}
box.style.left = `${left++}px`
} else {
if (left <= 0) {
flag = true
}
box.style.left = `${left--}px`
}
}
(function animloop() {
render()
window.requestAnimationFrame(animloop)
})()
以上便可讓html中的圓形左右動起來。
如果想讓動畫停下來需要怎么處理呢,是否可以像clearInterval這樣的方式呢?其實rAF提供了cancelAnimationFrame方法來停止動畫處理,requestAnimationFrame默認會返回一個id,將id傳入cancelAnimationFrame中便可達到效果。
修改以上代碼:
var box = document.getElementById('box')
var flag = false
var left = 0
var rAFId = ''
function render() {
if (flag) {
if (left >= 100) {
flag = false
}
box.style.left = `${left++}px`
} else {
if (left <= 0) {
flag = true
}
box.style.left = `${left--}px`
}
}
(function animloop() {
render()
rAFId = window.requestAnimationFrame(animloop)
if (left == 50) {
cancelAnimationFrame(rAFId)
}
})()
這樣在圓運動到距左側50px的時候就停下來了。
自己的學習筆記,如果你剛好看到,希望也能幫着你,如有不正確的地方歡迎多多指出。
