文章目录
- 理解requestAnimationFrame的基本概念
-
- 定义
- 核心作用
- 与传统定时器实现的对比
-
- [setTimeout/setInterval 实现动画的优缺点](#setTimeout/setInterval 实现动画的优缺点)
- [requestAnimationFrame 的优点](#requestAnimationFrame 的优点)
- 典型应用场景
- requestAnimationFrame的工作原理
-
- 浏览器渲染流程详解
- [requestAnimationFrame 的调用时机](#requestAnimationFrame 的调用时机)
- 与浏览器刷新率同步的实现
- requestAnimationFrame的语法与参数
- 完整代码(渲染一个立方体的变体)
理解requestAnimationFrame的基本概念
定义
requestAnimationFrame
是浏览器提供的一个专门用于动画效果的API,它告诉浏览器您希望执行一个动画,并请求浏览器在下次重绘之前调用指定的函数来更新动画。
核心作用
- 与浏览器刷新率同步:自动匹配显示器的刷新率(通常60Hz,即每秒60次),确保动画流畅不丢帧
- 优化性能:当页面处于非活动状态(如标签页被隐藏)时自动暂停执行,减少资源消耗
- 浏览器优化:浏览器可以合并多个动画请求,进行统一优化处理
与传统定时器实现的对比
setTimeout/setInterval 实现动画的优缺点
优点:
- 兼容性更好(支持所有浏览器)
- 可以自由控制执行间隔时间
缺点:
- 性能问题 :
- 无法与屏幕刷新同步,可能导致过度绘制或丢帧
- 即使页面隐藏也会继续执行,浪费CPU资源
- 示例:用setInterval实现的动画在标签页后台运行时仍然消耗资源
- 时间不精确 :
- 受JavaScript事件循环影响,实际执行时间可能延迟
- 在低端设备上可能出现明显的卡顿现象
- 电池消耗 :
- 不会自动暂停,持续消耗设备电量
requestAnimationFrame 的优点
- 性能优化 :
- 自动匹配显示器刷新率,减少不必要的重绘
- 页面不可见时自动暂停,节省资源
- 示例:使用rAF的动画在切换标签页后自动停止
- 流畅度 :
- 确保动画在每次屏幕刷新时只更新一次
- 避免"布局抖动"问题(多个CSS属性同时变化时)
- 现代浏览器支持 :
- 包括自动降频(当设备刷新率降低时自动调整)
典型应用场景
- 复杂动画:如游戏、数据可视化等需要高性能的场景
- 响应式UI:需要与用户交互同步的界面动画
- 滚动效果:实现平滑的滚动和视差效果
requestAnimationFrame的工作原理
浏览器渲染流程详解
现代浏览器的渲染流程通常包含以下关键步骤:
- JavaScript 执行 :
- 执行同步 JavaScript 代码
- 处理事件回调
- requestAnimationFrame 回调在此阶段执行
- 样式计算(Style Calculation) :
- 计算应用于每个 DOM 元素的 CSS 样式
- 包括继承样式和层叠样式计算
- 布局(Layout/Reflow) :
- 计算每个元素在页面中的几何位置和大小
- 构建渲染树(Render Tree)
- 绘制(Paint) :
- 将元素转换为实际像素
- 生成绘制指令列表
- 合成(Composite) :
- 将不同层合并为最终图像
- 使用 GPU 加速处理
requestAnimationFrame 的调用时机
requestAnimationFrame(rAF) 的设计目的是与浏览器刷新率同步执行动画代码:
- 同步机制 :
- rAF 回调会在浏览器每次重绘前执行
- 通常与显示器刷新率同步(如 60Hz 显示器约 16.67ms 一次)
- 调用位置 :
- rAF 回调在渲染管线的 JavaScript 执行阶段执行
- 执行时机在样式计算和布局之前
- 性能优势 :
- 避免不必要的中间帧计算
- 当页面不可见或最小化时会自动暂停
与浏览器刷新率同步的实现
- 垂直同步(VSync)协调 :
- 浏览器会等待显示器发出的 VSync 信号
- rAF 回调队列在 VSync 信号到来时执行
- 60FPS 优化 :
- 这种递归调用方式确保与刷新率同步
js
function animate() {
// 动画逻辑
requestAnimationFrame(animate);
}
requestAnimationFrame(animate);
- 高刷新率设备适配 :
- 在 120Hz 显示器上会自动调整为约 8.3ms 执行一次
- 无需开发者手动调整时间间隔
requestAnimationFrame的语法与参数
语法
javascript
const requestID = requestAnimationFrame(callback);
参数说明
- callback:浏览器在下次重绘(repaint)前调用的函数,通常是动画的更新逻辑。
- 回调函数参数:
timestamp
(时间戳):由requestAnimationFrame
自动传入,表示回调被调用的时间(从页面加载开始的毫秒数)。可用于计算帧间隔时间(delta time),实现时间同步的动画。- 示例:
javascript
function animate(timestamp) {
console.log("当前帧时间戳:", timestamp);
// 动画逻辑...
}
requestAnimationFrame(animate);
- 返回值
- 类型:
number
(非零整数)- 描述:返回一个唯一标识符(ID),可用于取消动画帧请求(通过
cancelAnimationFrame
)。- 示例:
javascript
const animationID = requestAnimationFrame(animate);
// 取消动画
cancelAnimationFrame(animationID);
完整代码(渲染一个立方体的变体)
可以与上面这个帖子中的代码进行对比,稍微不同,在下面的截图中有具体标注,也就是白色框的部分。
js
<script setup lang="ts">
import * as THREE from 'three'
import { onMounted, ref } from 'vue'
const canvasThree = ref()
// 创建scene、camera、renderer
let scene: THREE.Scene, camera: THREE.PerspectiveCamera, renderer: THREE.WebGLRenderer, cube: THREE.Mesh
// 1.创建场景
function initScene() {
scene = new THREE.Scene()
}
// 2.创建相机
function initCamera() {
camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000,
)
camera.position.set(0, 0, 10)
scene.add(camera)
}
// 3.创建物体(几何体 + 材质 = 物体)
function initCube() {
const boxWidth = 1
const boxHeight = 1
const boxDepth = 1
const geometry = new THREE.BoxGeometry(boxWidth, boxHeight, boxDepth)
const material = new THREE.MeshBasicMaterial({ color: 0x44AA88 })
cube = new THREE.Mesh(geometry, material)
scene.add(cube)
}
// 4.渲染场景
function initRenderer() {
// 创建渲染器
renderer = new THREE.WebGLRenderer({ antialias: true, canvas: canvasThree.value })
// 设置渲染器尺寸
renderer.setSize(window.innerWidth, window.innerHeight)
}
// 5.动画循环渲染场景
function animate() {
requestAnimationFrame(animate)
// 渲染场景
renderer.render(scene, camera)
}
onMounted(() => {
// 初始化场景、相机、渲染器、物体
initScene()
initCamera()
initRenderer()
initCube()
// 启动动画循环
animate()
})
</script>
<template>
<canvas id="canvasThree" ref="canvasThree" />
</template>

这里就是我们在使用three.js这个库时,所应用的requestAnimationFrame
- 根据上面的理论知识,来理解这里的白色代码,整个代码,除了白色部分,在上个帖子已经说明。
- 重复下:就是我们要看到一个立方体,需要:创建场景、创建相机并放入场景中、渲染这个场景到我们的网页元素
- 这时,我们添加一个立方体,放入场景中,也是我们做好了上面的三步,事实上是渲染不出来的。
- 如果我们采用这次的动画函数来渲染,这样就可以了。(我相信根据上面的对requestAnimationFrame的理论介绍,细思是比较容易理解的。)
- 在前两个帖子,实现了官网的案例,以及对轨道控制器的使用,其实都有这个代码,这次,针对这个requestAnimationFrame,也就是我们的动画函数,做了更深的介绍。