Skip to content

使用 requestAnimationFrame 来提升动画性能

Posted on:May 20, 2018 at 09:42 AM

传统动画的弊端

在实际项目中我们经常会遇到生成动画的需求,传统方法是通过使用 setTimeout 和 setInterval 进行实现,但是定时器动画有两个弊端:

为了解决这些问题 HTML5 加入了 requestAnimationFrame

requestAnimationFrame?

MDN

window.requestAnimationFrame() 方法告诉浏览器您希望执行动画并请求浏览器在下一次重绘之前调用指定的函数来更新动画。该方法使用一个回调函数作为参数,这个回调函数会在浏览器重绘之前调用。

用法

你可以直接调用requestAnimationFrame(),也可以通过 window 来调用window.requestAnimationFrame()。 requestAnimationFrame()接收一个函数作为回调,返回一个 ID 值,通过把这个 ID 值传给window.cancelAnimationFrame()可以取消该次动画。

MDN 上给的例子:

var start = null;
var element = document.getElementById("SomeElementYouWantToAnimate");
element.style.position = "absolute";

function step(timestamp) {
  if (!start) start = timestamp;
  var progress = timestamp - start;
  element.style.left = Math.min(progress / 10, 200) + "px";
  if (progress < 2000) {
    window.requestAnimationFrame(step);
  }
}

例子

我们来试试生成一个旋转并逐渐变窄的方块,当窄到一定程度又会复原循环往复。 jsbin 看看效果

var rotate = 0;
var width = 400;
var element = document.getElementById("box");

function step(timestamp) {
  rotate += 10;
  element.style.transform = `rotate(${rotate}deg)`;
  window.requestAnimationFrame(step);
}

function small(timestamp) {
  width = width - 1;
  element.style.width = width + "px";
  if (width <= 50) {
    window.requestAnimationFrame(big);
  } else {
    window.requestAnimationFrame(small);
  }
}
function big() {
  width = width + 1;
  element.style.width = width + "px";
  if (width >= 400) {
    window.requestAnimationFrame(small);
  } else {
    window.requestAnimationFrame(big);
  }
}

window.requestAnimationFrame(step);
window.requestAnimationFrame(small);

浏览器兼容情况

我们来看一下[Can I Use](https://caniuse.com/## search=requestAnimationFrame)上的兼容情况: requestAnimationFrame 的兼容情况还是不错的(看不见 IE)

如果非要兼容 IE 的话可以用定时器来做一下兼容:

(function () {
  var lastTime = 0;
  var vendors = ["webkit", "moz"];
  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) {
      /*调整时间,让一次动画等待和执行时间在最佳循环时间间隔内完成*/
      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);
    };
})();