深入理解 requestAnimationFrame API
1. 简介
- 什么是
requestAnimationFrame?- 简述:
requestAnimationFrame是一个用于更新动画的 API,通过告诉浏览器你想执行动画,并要求在下一次重绘之前调用指定的回调函数。 - 背景介绍:最初网页动画使用
setTimeout或setInterval控制时间间隔,但这些方法容易产生卡顿、掉帧问题。
- 简述:
- 它相对于传统的
setTimeout和setInterval的优势- 性能提升:
requestAnimationFrame自动根据屏幕的刷新率来调整帧率,避免了硬编码的时间间隔。 - 浏览器优化:浏览器会在屏幕即将重绘之前自动调用回调,确保动画的最佳时机。
- 流畅动画:提高动画流畅度,减少卡顿,尤其在高性能需求场景下如游戏、视频渲染。
- 性能提升:
2. 工作原理
-
解释
requestAnimationFrame的基本工作机制- 浏览器每秒会更新屏幕多次(通常是60次),
requestAnimationFrame通过在每次更新前调用注册的回调函数,确保动画与屏幕刷新同步。 - 注册后的回调函数会被放入任务队列,等到下次重绘前执行。
- 帧率同步:
requestAnimationFrame会根据设备的帧率自动调整调用频率(例如高刷新率显示器会每秒多次调用)。
- 浏览器每秒会更新屏幕多次(通常是60次),
-
图解展示浏览器如何分发和执行任务
3. 使用场景
-
浏览器动画的流畅渲染
requestAnimationFrame适用于处理元素位移、缩放、旋转等动画,确保动画不会出现明显的卡顿或跳帧。
-
游戏开发中的实时更新与优化
- 实时刷新画面,保持游戏角色或场景的动态更新与用户操作的实时反馈。
-
动画节奏控制
requestAnimationFrame可配合 CSS 动画或其他 JavaScript 动画框架使用,用于控制动画的节奏和帧率。
-
节能与性能优化
- 如果标签页不可见,浏览器会自动暂停
requestAnimationFrame调用,减少不必要的 CPU 和内存消耗,节省电量。
- 如果标签页不可见,浏览器会自动暂停
4. 与 setTimeout/setInterval 的对比
-
通过实例代码展示三者的区别
-
setTimeout:每隔固定时间执行,但不会考虑屏幕刷新,容易导致掉帧。<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>setTimeout ex</title> <style> #box { width: 50px; height: 50px; background-color: red; position: absolute; top: 100px; } </style> </head> <body> <div id="box"></div> <script> let box = document.getElementById('box'); let position = 0; function moveBox() { position += 5; // 每次移动5px box.style.transform = `translateX(${position}px)`; if (position < window.innerWidth - 50) { setTimeout(moveBox, 16); // 每16ms执行一次,大约为60fps } } setTimeout(moveBox, 16); // 初始调用 </script> </body> </html> -
setInterval:持续执行,但时间不精确,可能与屏幕刷新不同步,与setTimeout相同,也会容易导致掉帧。 -
requestAnimationFrame:基于帧率调用,能与屏幕刷新完全同步。<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>requestAnimationFrame ex</title> <style> #box { width: 50px; height: 50px; background-color: blue; position: absolute; top: 300px; } </style> </head> <body> <div id="box"></div> <script> let box = document.getElementById('box'); let position = 0; // 非常的丝滑 function moveBox() { position += 2; // 每次移动2px box.style.transform = `translateX(${position}px)`; if (position < window.innerWidth - 50) { requestAnimationFrame(moveBox); // 基于屏幕刷新率调用 } } requestAnimationFrame(moveBox); // 初始调用 </script> </body> </html>
-
-
为什么
requestAnimationFrame更适合动画?- 自动同步屏幕刷新,避免不必要的计算。
- 更好的性能:减少 CPU 使用率,使动画更加平滑。
- 动态适应不同设备(如高帧率显示器或移动设备)。
5. 使用示例
-
基本使用方法
- 如何通过
requestAnimationFrame创建简单的动画循环:function animate() { // 动画逻辑 console.log("动画帧"); requestAnimationFrame(animate); } requestAnimationFrame(animate); - 示例:一个不断移动的方块,每帧更新方块的位置,请看上文代码。
- 如何通过
-
高级应用:取消与嵌套
requestAnimationFrame- 如何使用
cancelAnimationFrame- 在特定条件下结束动画循环:
// 例如在达到一定位置的时候,停止动画 if (position < window.innerWidth - 50) { requestAnimationFrame(moveBox); // 基于屏幕刷新率调用 }
- 在特定条件下结束动画循环:
- 嵌套
requestAnimationFrame-
当多个动画需要协调执行时,可在一个动画结束后嵌套调用下一个动画。
-
例如,实现连续或连贯的动画序列:一个方块移动到一边后,开始改变颜色。
function moveBox() { position += 2; // 每次移动2px box.style.transform = `translateX(${position}px)`; if (position < window.innerWidth - 50) { requestAnimationFrame(changeBgColor); // 基于屏幕刷新率调用 } } function changeBgColor() { const randomIndex = Math.floor(Math.random()*3); box.style.backgroundColor = ['red', 'green', 'blue'][randomIndex]; requestAnimationFrame(moveBox); }
-
- 如何使用
-
结合实际开发中的优化策略
-
如何在长时间动画中避免性能瓶颈,例如分帧渲染、减少 DOM 操作、避免强制布局、调节帧率等。
- 分帧渲染:将任务分成小块,分散到多个帧中。
- 减少 DOM 操作:批量操作 DOM,避免频繁修改。
- 避免强制布局:避免在同一帧内同时读取和修改 DOM。
- 调节帧率:根据需求降低帧率,减少调用频率。
// 优化策略还有很多啊,小编这里采取了调节帧率的方式 let lastTime = 0; const fps = 10; function moveBox() { position += 5; // 每次移动5px box.style.transform = `translateX(${position}px)`; if (position < window.innerWidth - 50) { requestAnimationFrame(changeBgColor); // 基于屏幕刷新率调用 } } function changeBgColor(timestamp) { // 当前时间 - 上一次记录时间 >= 一秒内渲染一次的间隔时间 if (timestamp - lastTime >= 1000 / fps) { // 更新动画 const randomIndex = Math.floor(Math.random()*3); box.style.backgroundColor = ['red', 'green', 'blue'][randomIndex]; lastTime = timestamp; } requestAnimationFrame(moveBox); }
-
6. 性能优化与陷阱
- 提高动画性能的技巧
- 减少主线程阻塞:避免在
requestAnimationFrame回调中执行重型操作,如大量 DOM 操作或复杂计算。 - 避免重复计算和重绘:只在必要时更新元素,减少无效的重绘和重排。
- 减少主线程阻塞:避免在
- 常见陷阱与误区
- 过度使用:虽然
requestAnimationFrame更高效,但过度嵌套或不恰当的使用仍可能导致页面性能下降。 - 内存泄漏问题:如果忘记使用
cancelAnimationFrame取消不必要的动画,可能会导致动画持续执行,造成内存泄漏。
- 过度使用:虽然