来自requestAnimationFrame的灵魂拷问

1,266 阅读2分钟

大部分浏览器的显示频率为什么是16.7ms?

因为浏览器的刷新频率为每秒 60 帧 ,1 秒=1000 毫秒, 1000/60=16.7ms

浏览器出现丢帧现象的原因?

假如开一个10mssetTimeout,也就是 30ms 绘制 3 次,但是浏览器本来是 16.7ms 绘制一次,在 30 秒内只能绘制两次。那么就会造成丢帧的现象。(本来 30ms 要绘制 3 次,结果只只绘制了 2 次,丢了一帧),继而导致动画断续显示(堵车的感觉),这就是过度绘制带来的问题。也是 setTimeout 的定时器值推荐最小使用16.7ms的原因。

浏览器丢帧会造成什么问题?

  • 导致动画断续显示
  • 过度绘制会对电池使用寿命造成负面影响,并会降低其他应用的性能

描述一下运行机制?

  • requestAnimationFrame 会把每一帧中的所有 DOM 操作集中起来,在一次重绘或回流中就完成,并且重绘或回流的时间间隔紧紧跟随浏览器的刷新频率,一般来说,这个频率为每秒 60 帧

  • 在隐藏或不可见的元素中,requestAnimationFrame 将不会进行重绘或回流,这当然就意味着更少的的 cpu,gpu 和内存使用量

用法?

  • requestAnimationFrame 调用一下 只走一次,若要形成动画需要递归调用。
  • 参数:动画的回调函数
  • 返回值:id 动画帧编号
  • 清除动画 cancelAnimationFrame(id)
  let timer = 0
  function move() {
    if (true) {
      cancelAnimationFrame(timer)
    } else {
      timer = requestAnimationFrame(move)
    }
  }
  move()

与css 动画哪个性能更好?

没有做过比较,只能说两者不相上下。

兼容性如何?

IE10+,Firefox,Chrome,Safari,Opera 等,在移动设备上,ios6 以上版本以及 IE mobile 10 以上支持 requestAnimationFrame,唯一比较遗憾的是目前安卓上的原生浏览器并不支持 requestAnimationFrame,不过对 requestAnimationFrame 的支持应该是大势所趋了,安卓版本的 chrome 16+也是支持 requestAnimationFrame 的

如何兼容浏览器(即使ie6也能支持)?

(function () {
	// 兼容webkit,moz内核
	window.requestAnimationFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame;
	//取消动画帧有两个版本 cancelAnimationFrame, cancelRequestAnimationFrame
	window.cancelAnimationFrame = window.cancelAnimationFrame || window.webkitCancelAnimationFrame || window.mozCancelAnimationFrame || window.cancelRequestAnimationFrame || window.webkitCancelRequestAnimationFrame || window.mozCancelRequestAnimationFrame;
	if (!window.requestAnimationFrame) {
		// setTimeout模拟动画帧
		var lastTime = Date.now(); // 时间戳
		window.requestAnimationFrame = function (callback) {
			var id;
			var nowTime = Date.now();
			//并不是所有浏览器的屏幕的渲染频率为16.7,如果上一次的时间 和 本次时间 执行的间隔大于 16.7,那就不再延迟直接执行
			var delay = Math.max(16.7 - (nowTime - lastTime), 0);
			id = setTimeout(callback, delay);
			lastTime = nowTime + delay; //上一次动画执行的时间
			return id;
		};
	}
	// 取消动画帧
	if (!window.cancelAnimationFrame) {
		window.cancelAnimationFrame = function (id) {
			clearTimeout(id);
		};
	}
})();

升级版

(function () {
  var lastTime = 0;
  var vendors = ['webkit', 'moz'];
  // 在for循环当中做判断 !window.requestAnimationFrame
  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) {
          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);
      };
  }
}());