requestAnimationFrame——HTML5动画利器

简述

  requestAnimationFramewindow 对象中的一个新方法,是专门为了网页动画而生的。

优点

  在该方法没有实现之前,在操作 DOM 动画时,几乎只能通过 setInterval 定时器来实现。而 setInterval 方法实现动画的缺点是显而易见的:

  1. 时间计算不精准。由于 setInterval 处于 JavaScript 的异步执行阶段,基于 JavaScript单线程的特点,为其设定执行时间无法保证准确。
  2. 性能浪费。使用 setInterval 方法,我们需要考虑为其设定合适的间隔值,以保证动画流畅。而过低的间隔将造成事件频繁的触发降低代码性能。
  3. 不合预期的表现。使用 setInterval 设置动画时,当用户切换至其他页面或最小化时,setInterval 会暂时进入“休眠”状态,但是并不是不执行程序,它会把需要执行的操作放在队列中 ,等到下次窗口一打开的一瞬间把队列里面的全部执行。这样就造成了动画效果混乱,不合预期。

  而 requestAnimationFrame 则很好的避免了以上几点:

  1. 动画的调用时间是浏览器通过系统时间设定,是浏览器级别的操作,保证了时间的精确。
  2. 该方法只接受一个回掉函数作为参数,由浏览器设置合适的时间间隔(浏览器厂商根据屏幕刷新率来设定,通常为1/60秒)。同时浏览器会把该时刻所有的 DOM 操作集中起来完成(优化),在一次重绘或回流中完成。在复杂动画情况下,性能极大提升。并且该方法会忽略不可见元素的动画操作。
  3. 如果页面不处于激活状态,会自动暂停动画的执行。

语法

  requestAnimationFrame 方法的语法非常简单,只需要传入一个回调函数即可:

1
2
3
4
window.requestAnimationFrame(callback);
function callback() {
/* some animation code */
}

  该方法返回一个 long 非零整数,请求 ID ,也是回调列表中唯一的标识。

  需要注意的是,该方法的使用和 setTimeout 的时候略有不同。MDN 中对该方法的解释如下:

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

  也就是说,传入的回调函数只会在下一次重绘中被调用,仅此一次,而不是像 setTimeout 一次设置,循环调用。所以 MDN 中给出了这样的提示:

注意:若您想要在下次重绘时产生另一个动画画面,您的回调例程必须调用 requestAnimationFrame()。

  另外需要注意,该方法会为 callback 方法传入一个默认参数,该参数是一个时间戳,用以计算该动画从开始到该次回调之间的毫秒数。如果不注意,当需要对回调函数绑定参数并且参数缺省时可能会产生误解。

例子


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const ball = document.getElementById('ball');

let x = 5; // 初始高度
let t = 1; // 时间间隔
let a = 0.5 // 加速度
let v = 0; // 初速度
let maxX = 400; // 最大位移(反弹位置)

function drop() {
ball.style.top = Math.floor(x) + 'px';
if (x > maxX){
v = -v;
} else {
v += a * t;
}
x += v * t;
window.requestAnimationFrame(drop);
}
window.requestAnimationFrame(drop);
//drop();

  上面的例子模拟了一个小球的无能量损耗的自由落体及反弹运动(HTML 和 CSS 代码未给出)。其中动画函数是 drop,在该函数中计算小球的位移并复制给小球 DOM 的 top 属性。

  我们希望动画循环执行,至调用一次 window.requestAnimationFrame(drop) 是不行的,因为这只会在下一次重绘时更新。所以我们在 drop 函数末尾再次调用 window.requestAnimation(drop) 保证每次都有下一次,即实现了动画循环。

  如果希望动画在特定条件下停止,在函数中调用 window.requestAnimationFramereturn 即可。