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,也就是我们的动画函数,做了更深的介绍。
相关推荐
要天天开心啊3 小时前
Java序列化和反序列化
java·开发语言
二宝1523 小时前
黑马商城day1-MyBatis-Plus
java·开发语言·mybatis
前端开发呀3 小时前
无所不能的uniapp拦截器【三】uni-app 拦截器核心流程解析
前端·javascript·微信小程序
Porunarufu3 小时前
JAVA·类和对象③封装及包
java·开发语言
云和数据.ChenGuang3 小时前
vue中构建脚手架
前端·javascript·vue.js
渣哥3 小时前
面试官最爱刁难:Spring 框架里到底用了多少经典设计模式?
javascript·后端·面试
朱昆鹏4 小时前
如何通过sessionKey 登录 Claude
前端·javascript·人工智能
wdfk_prog4 小时前
klist 迭代器初始化:klist_iter_init_node 与 klist_iter_init
java·前端·javascript
煜3644 小时前
C++异常与智能指针
开发语言·c++