requestAnimationFrame()
是浏览器提供的一个 API,专门用于优化网页中的动画表现。它能够在合适的时机更新动画,减少资源浪费,并且提供更流畅的体验。相比传统的 setTimeout
或 setInterval
,requestAnimationFrame
的优势非常明显,尤其是在复杂动画的处理上。
核心作用
-
与浏览器刷新率同步
浏览器通常以 60Hz 的刷新率渲染页面,这意味着每秒刷新 60 次。requestAnimationFrame()
会确保动画的更新与显示器的刷新率同步,从而避免了不必要的计算,降低了卡顿现象,使得动画更加流畅。 -
自动暂停优化性能
当页面切换到后台(比如用户切换标签页、最小化浏览器等),requestAnimationFrame()
会自动暂停调用回调函数,减少 CPU/GPU 的消耗。这意味着如果用户不在查看动画,浏览器就不会继续进行不必要的计算,节省了系统资源。 -
浏览器级优化
浏览器在每一帧的渲染过程中会合并多个动画更新,优化渲染顺序。这种优化避免了因多次重绘和回流导致的性能问题,从而大幅提升动画效果的流畅度。
基本用法
function animate() {
// 更新动画状态(例如修改 DOM 样式、Canvas 绘制等)
updateAnimation();
// 递归调用,准备下一帧
requestAnimationFrame(animate);
}
// 启动动画
requestAnimationFrame(animate);
代码分析:
- 递归调用:
animate()
会在每一帧结束时,通过requestAnimationFrame(animate)
递归调用自己。这使得动画能够持续运行,直到你停止它。 timestamp
参数:回调函数接收一个时间戳(timestamp
),表示动画请求的时间。这个时间戳可以用于计算帧与帧之间的差值,实现平滑的动画过渡。
与传统方法对比
方法 | 问题 | requestAnimationFrame 的优势 |
---|---|---|
setTimeout(fn, 16) | 时间不精确,可能因事件循环阻塞导致丢帧 | 与屏幕刷新率同步,避免丢帧 |
setInterval(fn, 16) | 持续执行,即使页面隐藏;回调堆积可能导致性能问题 | 页面隐藏时自动暂停,减少 CPU/GPU 负担 |
requestAnimationFrame() | 仅在浏览器下一次重绘前调用,避免不必要的帧更新,确保流畅的动画效果 | 自动优化性能,与浏览器刷新率同步,智能暂停和合并渲染任务 |
为什么 requestAnimationFrame
优于 setTimeout
或 setInterval
?
- 精确性:
setTimeout
和setInterval
设定的时间间隔不是精确的,可能受到事件循环、网络请求或其他任务的阻塞影响,从而导致帧的丢失。相比之下,requestAnimationFrame
会在浏览器的下一个重绘周期前执行,确保动画更新不会错过。 - 性能:
requestAnimationFrame
会在页面不可见(如切换标签页或最小化窗口)时暂停,避免浪费计算资源,而setTimeout
和setInterval
即使在页面不显示时也会继续执行。
高级用法:使用 timestamp
进行平滑动画
requestAnimationFrame
提供了一个时间戳(timestamp
)作为回调参数,代表当前帧的执行时间。你可以利用这一信息实现更精确的动画过渡,尤其是涉及动画缓动(easing)或根据时间间隔调整动画速率时。
示例:平滑动画过渡
let lastTime = 0;
function animate(timestamp) {
if (lastTime) {
const delta = timestamp - lastTime; // 计算与上一帧的时间差
// 基于 delta 计算动画进度
const progress = Math.min(delta / 16.67, 1); // 16.67ms 对应 60fps
// 更新动画状态,例如根据进度改变位置、透明度等
updateAnimation(progress);
}
lastTime = timestamp;
requestAnimationFrame(animate);
}
requestAnimationFrame(animate);
通过 timestamp
,你可以精准地控制动画过渡速度,避免过快或过慢的更新。
适用场景
- 高频动画:如 CSS 动画、Canvas/WebGL 绘图、游戏场景渲染等。
- 滚动效果:如 Parallax 滚动,页面滚动时动画效果。
- 拖拽操作:响应用户拖动操作的实时动画更新。
- 物理模拟:如模拟小球弹跳、惯性滑动等物理动画效果。
注意事项
-
回调参数:
timestamp
每次调用requestAnimationFrame()
时,回调函数会接收到一个参数timestamp
,它表示自页面加载以来的时间戳(单位:毫秒)。这个时间戳可以帮助你精确地计算动画的时间差,避免过快或过慢的动画效果。requestAnimationFrame((timestamp) => { console.log("当前时间戳:", timestamp); });
-
递归调用
要保持动画持续更新,需要在每一帧中递归调用requestAnimationFrame()
,否则动画会停止一次。 -
浏览器兼容性
现代浏览器都支持requestAnimationFrame
,但老旧浏览器可能不支持。为了保证兼容性,可以使用 polyfill(如rAF
的兼容性库)来模拟该功能。
示例:在页面中平移一个元素
<div id="box" style="width: 50px; height: 50px; background: red;"></div>
<script>
const box = document.getElementById("box");
let position = 0;
function move(timestamp) {
position += 2;
box.style.transform = `translateX(${position}px)`;
// 如果位置未到达目标,继续请求下一帧动画
if (position < 200) {
requestAnimationFrame(move);
}
}
requestAnimationFrame(move);
</script>
此示例演示了如何通过 requestAnimationFrame
移动一个元素。通过递归调用 requestAnimationFrame(move)
,元素会平滑地从左向右移动,直到达到 200px 的位置。
进一步的优化与扩展
-
请求帧率的限制
如果你希望控制动画的帧率,可以在每次更新动画状态时添加一定的帧率控制(例如每 16.67 毫秒更新一次)。你也可以通过delta
来计算每帧的间隔时间,从而实现不同帧率的平滑效果。 -
多屏支持
在多屏环境中,requestAnimationFrame()
会基于主显示器的刷新率进行同步。如果你的应用需要支持不同屏幕刷新率(如 144Hz 的显示器),你可能需要做额外的处理来优化高刷显示器的性能。
总结
requestAnimationFrame()
是实现高效流畅动画的首选 API。它与浏览器的刷新机制深度集成,能够最大限度地减少性能开销,并自动暂停后台任务,有效节省资源。无论是简单的 UI 动画,还是复杂的游戏渲染,requestAnimationFrame
都能提供卓越的性能表现。