Vue3+Three.js:requestAnimationFrame的详细介绍
文章目录
- 理解requestAnimationFrame的基本概念
- 定义
- 核心作用
- 与传统定时器实现的对比
- setTimeout/setInterval 实现动画的优缺点
- 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 优化:
- 这种递归调用方式确保与刷新率同步
function animate() {// 动画逻辑requestAnimationFrame(animate);
}
requestAnimationFrame(animate);
- 高刷新率设备适配:
- 在 120Hz 显示器上会自动调整为约 8.3ms 执行一次
- 无需开发者手动调整时间间隔
requestAnimationFrame的语法与参数
语法
const requestID = requestAnimationFrame(callback);
参数说明
- callback:浏览器在下次重绘(repaint)前调用的函数,通常是动画的更新逻辑。
- 回调函数参数:
timestamp
(时间戳):由requestAnimationFrame
自动传入,表示回调被调用的时间(从页面加载开始的毫秒数)。可用于计算帧间隔时间(delta time),实现时间同步的动画。- 示例:
function animate(timestamp) {console.log("当前帧时间戳:", timestamp);// 动画逻辑...
}
requestAnimationFrame(animate);
- 返回值
- 类型:
number
(非零整数)- 描述:返回一个唯一标识符(ID),可用于取消动画帧请求(通过
cancelAnimationFrame
)。- 示例:
const animationID = requestAnimationFrame(animate);// 取消动画cancelAnimationFrame(animationID);
完整代码(渲染一个立方体的变体)
Vue3+Three.js打造3D立方体
可以与上面这个帖子中的代码进行对比,稍微不同,在下面的截图中有具体标注,也就是白色框的部分。
<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 = 1const boxHeight = 1const boxDepth = 1const 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,也就是我们的动画函数,做了更深的介绍。