requestAnimationFrame實現動畫的優點

在web應用中,實現動畫的方式有不少種。css

  1. js使用setTimeout實現
  2. css使用transition和animation實現
  3. html5使用canvas實現 除此以外就是咱們今天要介紹的requestAnimationFrame,從字面意思翻譯來看,就是請求動畫幀。

屏幕刷新頻率

刷新頻率即屏幕上的更新速度,或者說是屏幕上的圖像每秒鐘出現的次數,單位是赫茲(Hz),常見的電腦頻率爲60Hz。 顯示器也會以每秒60次的頻率正在不斷的更新屏幕上的圖像。爲何你感受不到這個變化? 那是由於人的眼睛有視覺停留效應,即前一副畫面留在大腦的印象還沒消失,緊接着後一副畫面就跟上來了,這中間只間隔了16.7ms(1000/60≈16.7), 因此會讓你誤覺得屏幕上的圖像是靜止不動的。而屏幕給你的這種感受是對的,試想一下,若是刷新頻率變成1次/秒,屏幕上的圖像就會出現嚴重的閃爍,這樣就很容易引發眼睛疲勞、痠痛和頭暈目眩等症狀。html

requestAnimationFrame

window.requestAnimationFrame() 方法告訴瀏覽器您但願執行動畫並請求瀏覽器在下一次重繪以前調用指定的函數來更新動畫。該方法使用一個回調函數做爲參數,這個回調函數會在瀏覽器重繪以前調用。html5

與setTimeout相比,requestAnimationFrame最大的優點是由系統來決定回調函數的執行時機。具體一點講,若是屏幕刷新率是60Hz,那麼回調函數就每16.7ms被執行一次,若是刷新率是75Hz,那麼這個時間間隔就變成了1000/75=13.3ms,換句話說就是,requestAnimationFrame的步伐跟着系統的刷新步伐走。它能保證回調函數在屏幕每一次的刷新間隔中只被執行一次,這樣就不會引發丟幀現象,也不會致使動畫出現卡頓的問題。git

下面咱們使用requestAnimationFrame實現進度條:github

<div class="box"></div>
<button class="btn">運行</button>
複製代碼
.box {
  background: hotpink;
  width: 0;
  height: 20px;
  line-height: 20px;
  border-radius: 10px;
  color: #fff;
  box-shadow: 0 0 10px hotpink;
  text-align: center;
}

.btn {
  background: skyblue;
  padding: 10px;
  margin-top: 20px;
  outline: 0;
  color: #fff;
  border-radius: 10px;
}
複製代碼
let timer
window.onload = () => {
  const btn = document.querySelector('.btn')
  const box = document.querySelector('.box')
  const width = 500
  function callback () {
    if (parseInt(box.style.width) < width) {
      box.style.width = parseInt(box.style.width) + 5 + 'px'
      box.innerHTML = parseInt(box.style.width) / (width / 100) + '%'
      timer = requestAnimationFrame(callback)
    } else {
      cancelAnimationFrame(timer)
    }
  }
  btn.onclick = () => {
    box.style.width = '0'
    cancelAnimationFrame(timer)
    timer = requestAnimationFrame(callback)
  }
}
複製代碼

image.png

除此以外,requestAnimationFrame還有如下兩個優點:web

  • CPU節能:使用setTimeout實現的動畫,當頁面被隱藏或最小化時,setTimeout 仍然在後臺執行動畫任務,因爲此時頁面處於不可見或不可用狀態,刷新動畫是沒有意義的,徹底是浪費CPU資源。而requestAnimationFrame則徹底不一樣,當頁面處理未激活的狀態下,該頁面的屏幕刷新任務也會被系統暫停,所以跟着系統步伐走的requestAnimationFrame也會中止渲染,當頁面被激活時,動畫就從上次停留的地方繼續執行,有效節省了CPU開銷。canvas

  • 函數節流:在高頻率事件(resize,scroll等)中,爲了防止在一個刷新間隔內發生屢次函數執行,使用requestAnimationFrame可保證每一個刷新間隔內,函數只被執行一次,這樣既能保證流暢性,也能更好的節省函數執行的開銷。一個刷新間隔內函數執行屢次時沒有意義的,由於顯示器每16.7ms刷新一次,屢次繪製並不會在屏幕上體現出來。瀏覽器

兼容性

requestAnimationFrame的兼容性還有些問題,之後可能不會繼續維護,因此能夠經過polyfill的方式來兼容。函數

;(function (window) {
  var lastTime = 0;
  var prefixes = 'webkit moz ms o'.split(' '); //各瀏覽器前綴

  var requestAnimationFrame = window.requestAnimationFrame;
  var cancelAnimationFrame = window.cancelAnimationFrame;

  var prefix;
  //經過遍歷各瀏覽器前綴,來獲得requestAnimationFrame和cancelAnimationFrame在當前瀏覽器的實現形式
  for( var i = 0; i < prefixes.length; i++ ) {
    if ( requestAnimationFrame && cancelAnimationFrame ) {
      break;
    }
    prefix = prefixes[i];
    requestAnimationFrame = requestAnimationFrame || window[ prefix + 'RequestAnimationFrame' ];
    cancelAnimationFrame  = cancelAnimationFrame  || window[ prefix + 'CancelAnimationFrame' ] || window[ prefix + 'CancelRequestAnimationFrame' ];
  }

  //若是當前瀏覽器不支持requestAnimationFrame和cancelAnimationFrame,則會退到setTimeout
  if ( !requestAnimationFrame || !cancelAnimationFrame ) {
    requestAnimationFrame = function( callback ) {
      var currTime = new Date().getTime();
      //爲了使setTimteout的儘量的接近每秒60幀的效果
      var timeToCall = Math.max( 0, 16 - ( currTime - lastTime ) ); 
      var id = window.setTimeout( function() {
      callback( currTime + timeToCall );
      }, timeToCall );
      lastTime = currTime + timeToCall;
      return id;
    };
    
    cancelAnimationFrame = function( id ) {
      window.clearTimeout( id );
    };
  }
  //獲得兼容各瀏覽器的API
  window.requestAnimationFrame = requestAnimationFrame; 
  window.cancelAnimationFrame = cancelAnimationFrame;
})(window)
複製代碼

博客地址 gitbook小冊動畫

相關文章
相關標籤/搜索