目录
requestAnimationFrame vs setTimeout:两者的性能差异及应用场景
1. setTimeout 和 requestAnimationFrame 简介
requestAnimationFrame vs setTimeout:两者的性能差异及应用场景
在前端开发中,尤其是在进行动画、滚动、视觉效果等涉及时间控制的操作时,如何优化性能是非常重要的。requestAnimationFrame
和 setTimeout
都是常见的控制动画更新的方式,但它们在性能、精度以及资源消耗方面存在显著差异。本文将深入探讨这两者的区别,并通过实例来展示它们在实际开发中的应用和性能差异。
1. setTimeout
和 requestAnimationFrame
简介
-
setTimeout
:setTimeout
是 JavaScript 中用于延迟执行某个函数的内置方法。它可以设定某个函数在指定的时间后被调用一次。它的基本语法是:setTimeout(callback, delay);
callback
是回调函数,delay
是延迟的时间,单位为毫秒。setTimeout
常用于定时执行任务,但在动画的应用中,它并不是最佳选择,因为它会基于指定的时间间隔来执行任务,而不与浏览器的刷新率同步。 -
requestAnimationFrame
:requestAnimationFrame
是浏览器提供的 API,用于在浏览器下次重绘前执行动画。它能够保证动画与浏览器的刷新频率同步,从而避免了不必要的资源浪费。它的基本语法是:requestAnimationFrame(callback);
callback
是回调函数,通常用来更新动画的状态。requestAnimationFrame
会根据浏览器的刷新率自动调整调用频率,通常是每秒 60 次,即 60 FPS(帧每秒)。
2. 性能差异与资源消耗
-
流畅度与同步:
requestAnimationFrame
是为动画设计的 API,它会在浏览器的重绘周期内执行,通常与显示器的刷新频率同步。这样可以确保动画的平滑性,并避免出现卡顿现象。与此不同,setTimeout
是基于时间间隔的,它并不会考虑浏览器的刷新频率,因此即使设置了setTimeout
触发动画更新,它的执行频率也并不稳定,可能导致动画出现不流畅或丢帧的现象。 -
CPU 与内存占用: 一个显著的差异是,
requestAnimationFrame
会在页面不可见时(如切换到后台 tab)暂停执行,从而节省了 CPU 和内存资源。它在用户再次切回页面时恢复动画。而setTimeout
则会继续执行,导致不必要的资源消耗,尤其是当页面在后台时,setTimeout
仍然会频繁执行,浪费 CPU 资源。
3. 实际应用场景中的区别:
通过简单的例子,我们可以明显看到两者的区别。假设我们要实现一个小球上下弹跳的动画:
-
使用
setTimeout
: 即使切换到后台 tab,动画仍然继续运行,导致 CPU 使用率高,并且动画可能卡顿。 -
使用
requestAnimationFrame
: 切换到后台 tab 后,动画会暂停,CPU 使用率大大减少;当用户切回页面时,动画从暂停的地方继续,并且保持流畅。
通过这种方式,requestAnimationFrame
显著提高了性能,并且在移动端和低性能设备上尤其能够节省大量资源。
4. 性能测试与帧率 (FPS) 比较
为了直观地展示差异,我们可以通过计算每秒帧数(FPS)来比较 setTimeout
和 requestAnimationFrame
的表现:
-
FPS 计算: FPS 反映了每秒更新多少帧图像。在理想情况下,动画的 FPS 应该接近 60,这对应于浏览器的刷新频率。
-
setTimeout
: 即使设置为每 16 毫秒调用一次,setTimeout
的执行并不会精确地与浏览器刷新同步,可能会导致每秒的帧数低于预期,尤其是在切换 tab 后,动画会继续执行,占用大量资源。 -
requestAnimationFrame
: 在浏览器的渲染循环中,requestAnimationFrame
会自动调节执行频率,保证每秒接近 60 帧。如果切换 tab,FPS 会降至 0,避免了无用的资源消耗。
5. 总结与最佳实践:
-
requestAnimationFrame
的优势:-
流畅性:与浏览器刷新率同步,保证动画平滑运行。
-
性能:切换到后台时自动暂停,节省系统资源。
-
精度:可以准确地计算动画帧,避免因定时器精度不高导致的卡顿。
-
浏览器优化:浏览器对
requestAnimationFrame
有专门优化,使其在性能上表现优越。
-
-
setTimeout
的缺点:-
不同步:无法与浏览器的渲染周期同步,可能导致动画不流畅。
-
资源浪费:在后台执行时,依然占用 CPU 资源,影响设备性能。
-
6.代码演示差距:
我们简单创建一个空文件写入以下内容:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>RequestAnimationFrame vs setTimeout</title>
<style>
body {
margin: 0;
overflow: hidden;
background-color: #333;
color: white;
font-family: Arial, sans-serif;
}
canvas {
display: block;
margin: 0 auto;
}
#controls {
position: absolute;
top: 10px;
left: 10px;
color: white;
font-size: 16px;
}
#fps {
position: absolute;
bottom: 10px;
left: 10px;
color: white;
font-size: 16px;
}
</style>
</head>
<body>
<div id="controls">
<button onclick="toggleAnimation()">切换动画</button>
</div>
<canvas id="canvas"></canvas>
<div id="fps"></div>
<script>
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
let width = canvas.width = window.innerWidth;
let height = canvas.height = window.innerHeight;
// 动画控制
let useRequestAnimationFrame = true; // 默认为使用 requestAnimationFrame
window.addEventListener('resize', () => {
width = canvas.width = window.innerWidth;
height = canvas.height = window.innerHeight;
});
// 小球对象
class Ball {
constructor() {
this.reset();
}
reset() {
this.x = Math.random() * width;
this.y = Math.random() * height / 2;
this.radius = 30;
this.dy = 2 + Math.random() * 4;
this.color = `hsl(${Math.random() * 360}, 100%, 70%)`;
}
update() {
this.y += this.dy;
if (this.y > height - this.radius || this.y < this.radius) {
this.dy *= -1;
}
}
draw() {
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
ctx.fillStyle = this.color;
ctx.fill();
}
}
const ball = new Ball();
// FPS 计算
let lastTime = 0;
let frames = 0;
let fps = 0;
function calculateFPS() {
frames++;
const now = performance.now();
const deltaTime = now - lastTime;
if (deltaTime >= 1000) {
fps = frames;
frames = 0;
lastTime = now;
document.getElementById("fps").innerText = `FPS: ${fps}`;
}
}
// 动画核心:使用 requestAnimationFrame 或 setTimeout 控制
let animationFrameId;
function animateWithRequestAnimationFrame() {
ctx.clearRect(0, 0, width, height);
ball.update();
ball.draw();
calculateFPS();
animationFrameId = requestAnimationFrame(animateWithRequestAnimationFrame);
}
function animateWithSetTimeout() {
ctx.clearRect(0, 0, width, height);
ball.update();
ball.draw();
calculateFPS();
setTimeout(animateWithSetTimeout, 16); // 16ms ≈ 60fps
}
function startAnimation() {
if (useRequestAnimationFrame) {
animateWithRequestAnimationFrame();
} else {
animateWithSetTimeout();
}
}
function toggleAnimation() {
useRequestAnimationFrame = !useRequestAnimationFrame;
cancelAnimationFrame(animationFrameId);
ball.reset();
startAnimation();
}
startAnimation();
</script>
</body>
</html>
7. 结语:
总的来说,requestAnimationFrame
是做动画的最佳选择,特别是在需要平滑、高效的动画体验时。而 setTimeout
由于其不稳定性和资源消耗问题,并不适合用于动画处理。在开发中,尽量选择 requestAnimationFrame
来优化动画效果,并保证更好的性能,尤其是在移动端和低性能设备上。