Vue3+Three.js:requestAnimationFrame的详细介绍

文章目录

理解requestAnimationFrame的基本概念

定义

requestAnimationFrame 是浏览器提供的一个专门用于动画效果的API,它告诉浏览器您希望执行一个动画,并请求浏览器在下次重绘之前调用指定的函数来更新动画。

核心作用

  1. 与浏览器刷新率同步:自动匹配显示器的刷新率(通常60Hz,即每秒60次),确保动画流畅不丢帧
  2. 优化性能:当页面处于非活动状态(如标签页被隐藏)时自动暂停执行,减少资源消耗
  3. 浏览器优化:浏览器可以合并多个动画请求,进行统一优化处理

与传统定时器实现的对比

setTimeout/setInterval 实现动画的优缺点

优点

  • 兼容性更好(支持所有浏览器)
  • 可以自由控制执行间隔时间
    缺点
  1. 性能问题
    • 无法与屏幕刷新同步,可能导致过度绘制或丢帧
    • 即使页面隐藏也会继续执行,浪费CPU资源
    • 示例:用setInterval实现的动画在标签页后台运行时仍然消耗资源
  2. 时间不精确
    • 受JavaScript事件循环影响,实际执行时间可能延迟
    • 在低端设备上可能出现明显的卡顿现象
  3. 电池消耗
    • 不会自动暂停,持续消耗设备电量

requestAnimationFrame 的优点

  1. 性能优化
    • 自动匹配显示器刷新率,减少不必要的重绘
    • 页面不可见时自动暂停,节省资源
    • 示例:使用rAF的动画在切换标签页后自动停止
  2. 流畅度
    • 确保动画在每次屏幕刷新时只更新一次
    • 避免"布局抖动"问题(多个CSS属性同时变化时)
  3. 现代浏览器支持
    • 包括自动降频(当设备刷新率降低时自动调整)

典型应用场景

  1. 复杂动画:如游戏、数据可视化等需要高性能的场景
  2. 响应式UI:需要与用户交互同步的界面动画
  3. 滚动效果:实现平滑的滚动和视差效果

requestAnimationFrame的工作原理

浏览器渲染流程详解

现代浏览器的渲染流程通常包含以下关键步骤:

  1. JavaScript 执行
    • 执行同步 JavaScript 代码
    • 处理事件回调
    • requestAnimationFrame 回调在此阶段执行
  2. 样式计算(Style Calculation)
    • 计算应用于每个 DOM 元素的 CSS 样式
    • 包括继承样式和层叠样式计算
  3. 布局(Layout/Reflow)
    • 计算每个元素在页面中的几何位置和大小
    • 构建渲染树(Render Tree)
  4. 绘制(Paint)
    • 将元素转换为实际像素
    • 生成绘制指令列表
  5. 合成(Composite)
    • 将不同层合并为最终图像
    • 使用 GPU 加速处理

requestAnimationFrame 的调用时机

requestAnimationFrame(rAF) 的设计目的是与浏览器刷新率同步执行动画代码:

  1. 同步机制
    • rAF 回调会在浏览器每次重绘前执行
    • 通常与显示器刷新率同步(如 60Hz 显示器约 16.67ms 一次)
  2. 调用位置
    • rAF 回调在渲染管线的 JavaScript 执行阶段执行
    • 执行时机在样式计算和布局之前
  3. 性能优势
    • 避免不必要的中间帧计算
    • 当页面不可见或最小化时会自动暂停

与浏览器刷新率同步的实现

  1. 垂直同步(VSync)协调
    • 浏览器会等待显示器发出的 VSync 信号
    • rAF 回调队列在 VSync 信号到来时执行
  2. 60FPS 优化
    • 这种递归调用方式确保与刷新率同步
js 复制代码
function animate() {
  // 动画逻辑
  requestAnimationFrame(animate);
}
requestAnimationFrame(animate);
  1. 高刷新率设备适配
    • 在 120Hz 显示器上会自动调整为约 8.3ms 执行一次
    • 无需开发者手动调整时间间隔

requestAnimationFrame的语法与参数

语法

javascript 复制代码
const requestID = requestAnimationFrame(callback);

参数说明

  1. callback:浏览器在下次重绘(repaint)前调用的函数,通常是动画的更新逻辑。
  • 回调函数参数:
    • timestamp(时间戳):由 requestAnimationFrame 自动传入,表示回调被调用的时间(从页面加载开始的毫秒数)。可用于计算帧间隔时间(delta time),实现时间同步的动画。
    • 示例:
javascript 复制代码
function animate(timestamp) {
  console.log("当前帧时间戳:", timestamp);
  // 动画逻辑...
}
requestAnimationFrame(animate);
  1. 返回值
  • 类型:number(非零整数)
  • 描述:返回一个唯一标识符(ID),可用于取消动画帧请求(通过 cancelAnimationFrame)。
  • 示例:
javascript 复制代码
     const animationID = requestAnimationFrame(animate);
     // 取消动画
     cancelAnimationFrame(animationID);

完整代码(渲染一个立方体的变体)

Vue3+Three.js打造3D立方体

可以与上面这个帖子中的代码进行对比,稍微不同,在下面的截图中有具体标注,也就是白色框的部分。

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,也就是我们的动画函数,做了更深的介绍。
相关推荐
程序猿_极客1 分钟前
JavaScript的Web APIs 入门到实战(day2):事件监听与交互实现,轻松实现网页交互效果(附练习巩固)
开发语言·前端·javascript·学习笔记·web apis 入门到实战
闲人编程15 分钟前
用Python控制硬件:Raspberry Pi项目初体验
开发语言·python·raspberry·pi·codecapsule·控制硬件
Mintopia19 分钟前
🚀 一文看懂 “Next.js 全栈 + 微服务 + GraphQL” 的整体样貌
前端·javascript·全栈
cherry--20 分钟前
集合(开发重点)
java·开发语言
Mintopia22 分钟前
🧬 医疗Web场景下,AIGC的辅助诊断技术边界与伦理
前端·javascript·aigc
半桶水专家26 分钟前
父子组件通信详解
开发语言·前端·javascript
Watermelo61729 分钟前
从vw/h到clamp(),前端响应式设计的痛点与进化
前端·javascript·css·算法·css3·用户界面·用户体验
Moment34 分钟前
快到  2026  年了:为什么我们还在争论  CSS 和 Tailwind?
前端·javascript·css
鸢尾掠地平39 分钟前
Python中常用内置函数上【含代码理解】
开发语言·python
梵得儿SHI1 小时前
Vue 核心语法详解:模板语法中的绑定表达式与过滤器(附 Vue3 替代方案)
前端·javascript·vue.js·插值语法·vue模板语法·绑定表达式·过滤器机制