JavaScript 中的动画问题

实现动画效果方式有很多,可以是 CSS3 的 transition 和 animation,可以是 JavaScript 的 setTimeout、setInterval, 也可以是 html5 的 RequestAnimationFrame。本文主要针对后两种方式做一下介绍。

一、setTimeout、setInterval

在 JavaScript 中创建动画的典型方式,就是使用 setInterval() 方法来控制所有动画。如下:

1
2
3
4
5
6
7
(function(){
function updateAnimations(){
doAnimation1();
doAnimation2(); //其他动画
}
setInterval(updateAnimations, 100);
})();

实现的想法是以一定的延迟时间不停的运行 updateAnimations() 方法,让它去运行每个动画并更改不同元素的状态。

最佳循环间隔:大多数电脑显示器的刷新频率是 60Hz,大概相当于每秒钟重绘 60 次。最平滑动画的最佳循环间隔是 1000ms/60,约等于 17ms

但实际的结果是传入的延迟时间只是指定了什么时候把动画代码添加到任务队列中以等待执行。如果队列前面已经加入了其他任务,那动画代码就要等前面的任务完成后再执行。因此setInterval()、setTimeout()都不是很精确。

引起问题的原因是我们想的是传入一个时间让这个函数多久再去运行,但实际浏览器是过了这么久才把它加到任务队列中,要想精确绘制动画,是要确定下一步开始绘制动画的时间,而不是加到队列中的时间,mozRequestAnimationFrame 就很好的解决了这个问题。

二、RequestAnimationFrame

与 setTimeout 和 setInterval 不同,requestAnimationFrame 只需要一个回调函数作为参数,不需要设置时间间隔, 它会告诉浏览器希望执行动画并请求浏览器在下一次重绘之前调用指定的函数来更新动画。

大多数电脑显示器的刷新频率是 60Hz,大概相当于每秒钟重绘 60 次。大多数浏览器都会对重绘操作加以限制,不超过显示器的重绘频率,因为即使超过那个频率用户体验也不会有提升。因此,最平滑动画的最佳循环间隔是 1000ms/60,约等于 16.6ms。

使用 RequestAnimationFrame 实现动画的代码如下:

1
2
3
4
5
6
7
8
function updateProgress(){
var div = document.getElementById("status");
div.style.width = (parseInt(div.style.width, 10) + 5) + "%";
if (div.style.left != "100%"){
RequestAnimationFrame(updateProgress);
} }
RequestAnimationFrame(updateProgress);
// 其实现的想法是 RequestAnimationFrame() 只运行一次传入的函数,在需要再次生成动画时需要再次手动调用它。同时也要考虑什么时候停止动画。这样就能得到非常平滑流畅的动画。

特点:

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

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

  • requestAnimationFrame 是由浏览器专门为动画提供的 API,在运行时浏览器会自动优化方法的调用,并且如果页面不是激活状态下的话,动画会自动暂停,有效节省了 CPU 开销。

下面就看个例子吧。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
#progress {
background-color: #95d1f7;
width: 0;
height: 40px;
line-height: 40px;
border-radius: 20px;
text-indent: 20px;
}
#btn {
width: 50px;
height: 30px;
margin: 10px;
}
</style>
</head>
<body>
<div id="progress">0%</div>
<button id="btn">btn</button>
<script>
var timer = null;
var btn = document.getElementById('btn');
btn.onclick = function(){
var progress = document.getElementById('progress');
progress.style.width = '0';
cancelAnimationFrame(timer);
timer = requestAnimationFrame(function fn(){
if(parseInt(progress.style.width) < 500){
progress.style.width = parseInt(progress.style.width) + 5 + 'px';
progress.innerHTML = parseInt(progress.style.width)/5 + '%';
timer = requestAnimationFrame(fn);
} else {
cancelAnimationFrame(timer); // 取消定时器
}
});
}
</script>
</body>
</html>
微信打赏