requestAnimationFrame基礎知識


最近工作中經常遇到動畫的情況,之前常用的方法使用setTimeout或setInterval實現,但隨着應用的越來越復雜,性能方面就會降低。所以選擇使用requestAnimationFrame來實現相同效果。本文簡單記錄使用rAF的方法。


requestAnimationFrameHTML5中提供的動畫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的時候就停下來了。

自己的學習筆記,如果你剛好看到,希望也能幫着你,如有不正確的地方歡迎多多指出。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM