從微信小程序重力感應API到requestAnimationFrame探索實現

最近作微信小程序的開發時,想作一個靠感知手機方向,使頁面上節點跟隨移動的動畫(即重力感應視差效果)功能。結果發現微信小程序有一些坑:javascript

在這個背景下,要實現平滑的重力感應的視差體驗就那麼優雅了,由於人對至少60幀每秒的動畫纔會感受流暢。最終實現的效果會有卡頓現象。java

實現期間,想起好像有requestAnimationFrame這個跟動畫相關的API,其功能表現與setTimeout相似,即隔一段時間調用一個回調函數。對於這個API,以前瞭解不深,此次拿起來產生了一個疑問:既生setTimeout,何生requestAnimationFrame?帶着疑問,開始調研。web

與setTimeout的不一樣

在MDN上,關於requestAnimationFrame的定義是:chrome

window.requestAnimationFrame()這個方法是用來在頁面重繪以前,通知瀏覽器調用一個指定的函數,以知足開發者操做動畫的需求。這個方法接受一個函數爲參,該函數會在重繪前調用。小程序

在這裏,我產生一個疑問,所謂的「在頁面重繪以前」,指的是,這個指定函數(如下簡稱cb)會在底層機制的運行下,在頁面重繪以前調用;仍是人爲地設置一個間隔時間,去調用cb,致使重繪?微信小程序

以前大概瞭解過頁面的重排和重繪。動畫能用重排和重繪來實現(這裏指的是能用這兩種途徑來達到動畫目的,而不是二者都適合用來實現動畫),而定義裏只提到重繪,沒提到重排的緣由,雖然我沒去細究,但很重要一點確定是由於,重繪性能遠高於重排。因此動畫不要經過left、margin等來實現,應該經過translate屬性來實現。api

既然說到translate,稍微延伸一下,動畫若是要用translate,最好用tranlate3d。由於較於tranlate,tranlate3d能獲得更完整的GPU加速的支持,使得性能更優。瀏覽器

言歸正傳,繼續解決剛剛的疑問。往下閱讀,發現這麼一段解釋:

若是你想作逐幀動畫的時候,你應該用這個方法。這就要求你的動畫函數執行會先於瀏覽器重繪動做。一般來講,被調用的頻率是每秒60次,可是通常會遵循W3C標準規定的頻率。若是是後臺標籤頁面,重繪頻率則會大大下降。

從這段話能夠看出,在用了這個方法後,瀏覽器會根據本身的重繪頻率,而每次重繪前會調用cb。用如下代碼驗證:

var laststart
function test () {
  laststart && console.log(Date.now() - laststart)
  laststart = Date.now()
  requestAnimationFrame(test)
}
requestAnimationFrame(test)

獲得結果,我所在的瀏覽器環境(mac + chrome[55.0.2883.95])的重繪頻率約爲60次每秒:

對此我產生幾個疑問:

  • 問題1:若是我干擾了重繪的頻率,是否還會是一個幾乎保持在每秒60幀的頻率呢(只針對提升頻率進行探究)?

  • 問題2:是否調用了requestAnimationFrame就會產生必定頻率的重繪?

  • 問題3:若是不調用requestAnimationFrame,在無其餘代碼去重繪頁面的話,頁面就不會重繪嗎?

爲了驗證問題1,我加了這麼一段代碼:

var x = 1
var style = document.querySelectorAll('.test')[0].style
function interference () {
  x *= -1
  style.transform = `translate3d(${x * 20}px, 0 ,0)`
  setTimeout(interference, 5)
}
interference()

獲得的結果與上一個結果一致。

由此得出結論:cb的調用頻率在人爲干擾重繪頻率的狀況下,依舊我行我素。

等等,人爲干擾重繪頻率成功了嗎?會不會雖然interference的調用頻率爲5ms一次,但瀏覽器的重繪頻率依舊是約等於60次每秒,即interference雖然試圖去觸發瀏覽器5ms重繪一次,但瀏覽器只會阻塞住,等下一次瀏覽器默認頻率重繪時再一塊兒重繪?

爲了探究這個問題,我將interference的setTimeout時間分別設置爲16ms(簡稱爲i16)、10ms(簡稱爲i10)、8ms(簡稱爲i8)、5ms(簡稱爲i5),若是瀏覽器重繪頻率沒法人爲干擾,因如下兩個緣由:

  • interference的函數對$('.test')的改變爲水平位移正負20px交替出現

  • 瀏覽器默認重繪頻率接近16ms一次

i8會因16ms中被調用2次,使得$('.test')迴歸原位而致使肉眼看到的$('.test')閃動頻率最慢;而i16的調用頻率和重繪頻率最爲接近,在這種狀況下,肉眼看到的$('.test')閃動頻率會是最快。

結果肉眼看到的閃動頻率從高到低依次是:i16 > i十、i5 > i8

故得出結論,用上面的方法,沒法人爲干擾瀏覽器默認的重繪頻率

那麼是否有辦法設置瀏覽器的重繪頻率呢?沒有查到直接答案,但在阮一峯的網頁性能管理詳解中,有提到:

大多數顯示器的刷新頻率是60Hz,爲了與系統一致,以及節省電力,瀏覽器會自動按照這個頻率,刷新動畫

證實瀏覽器的重繪頻率和顯示器的刷新頻率相等。因此應該沒有直接設置瀏覽器重繪頻率的方法,畢竟頁面重繪了,但顯示器沒刷新,影響了性能卻沒效果。

其實經過chrome自帶的開發者工具裏的timeline功能,就能清晰看到,上面的方法的間隔時間不管怎麼設置,都不會改變重繪頻率:

有了這個工具,其他兩個問題也迎刃而解:

  • 問題2的答案:調用了requestAnimationFrame就會產生必定頻率的重繪,只是這種狀況下的重繪會因並沒有實質重繪內容,而歷時極短。

  • 問題3的答案:若是不調用requestAnimationFrame,在無其餘代碼去重繪頁面的話,頁面就不會重繪

D436903A-740E-42B9-A817-7FEB7BF84C1B.png

調研到這一步,發現requestAnimationFrame和setTimeout根本不是一回事。requestAnimationFrame是一個根據瀏覽器重繪頻率來調用的方法,setTimeout則是一個計時器。定義不一樣,適用的場景也徹底不一樣,也沒有性能高低之分。

兼容性

MDN給出的requestAnimationFrame的兼容性以下:

也就是說requestAnimationFrame確定有兼容性問題。因此降級處理也是必須的。如下是降級代碼:

;(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) {
      // 保證若是重複執行callback的話,callback的執行起始時間相隔16ms
      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);
    };
  }

}());

requestAnimationFrame在微信小程序裏的表現

  • 微信iOS版小程序徹底不支持requestAnimationFrame

結論

  • requestAnimationFrame和setTimeout根本不是一回事,根據其定義,能夠在不一樣場景下使用。

  • 較於tranlate,tranlate3d能獲得更完整的GPU加速的支持。

  • 瀏覽器對頁面的重繪有一個默認的最大頻率,最大頻率沒法人爲設置,也沒有設置的必要。

  • 調用了requestAnimationFrame就會產生必定頻率的重繪,只是這種狀況下的重繪會因並沒有實質重繪內容,而歷時極短。

  • 若是不調用requestAnimationFrame,在無其餘代碼去重繪頁面的話,頁面就不會重繪。

最後,附上咱們趣店集團的小程序二維碼:

相關文章
相關標籤/搜索