VueUse useRafFn 完全指南:用 requestAnimationFrame 驱动高性能动画与帧回调

发布时间:2026/10/6 12:20:05
VueUse useRafFn 完全指南:用 requestAnimationFrame 驱动高性能动画与帧回调 前端【免费下载链接】vueuseCollection of essential Vue Composition Utilities for Vue 3项目地址https://gitcode.com/gh_mirrors/vu/vueuse点击查看免费下载导读useRafFn是 VueUse 中用于在每一帧requestAnimationFrame上调用回调函数的组合式函数并提供暂停pause与恢复resume的精细控制。它在动画循环、帧率统计、实时数据更新、游戏循环等场景中非常实用尤其适合需要高性能、节能的浏览器端逐帧任务。读完本文你将掌握useRafFn的完整 API、所有配置项immediate、fpsLimit、once、回调参数delta与timestamp的语义并能结合源码原理与测试用例在实际项目中写出正确、可控、可复用的逐帧逻辑。一、useRafFn 是什么useRafFn的作用非常明确在每个requestAnimationFrame上调用传入的函数并提供暂停和恢复的控制能力。它是 VueUse Animation动画分类下的核心工具之一位于 packages/core/useRafFn/index.ts并已从 packages/core/index.ts 的入口统一导出可通过以下方式直接使用import { useRafFn } from vueuse/core与直接裸写requestAnimationFrame递归不同useRafFn帮你封装了回调的递归调度无需手动维护rafId暂停 / 恢复 / 自动清理组件卸载时自动cancelAnimationFrame帧间隔delta与帧时间戳timestamp计算可选的帧率限制fpsLimit与一次性执行once。二、基础用法官方文档packages/core/useRafFn/index.md给出的最小示例import { useRafFn } from vueuse/core import { shallowRef } from vue const count shallowRef(0) const { pause, resume } useRafFn(() { count.value console.log(count.value) })这段代码会立刻开始逐帧执行回调默认immediate: true每帧让count加 1 并打印。调用pause()后循环暂停调用resume()后从下一帧恢复。仓库自带的演示组件 packages/core/useRafFn/demo.vue 展示了更贴近实战的用法——在 Vue 模板中展示当前帧数与帧间隔并通过按钮控制暂停与恢复script setup langts import { useRafFn } from vueuse/core import { shallowRef } from vue const fpsLimit 60 const count shallowRef(0) const deltaMs shallowRef(0) const { pause, resume } useRafFn(({ delta }) { deltaMs.value delta count.value 1 }, { fpsLimit }) /script template div font-mono Frames: {{ count }} /div div font-mono Delta: {{ deltaMs.toFixed(0) }}ms /div div font-mono FPS Limit: {{ fpsLimit }} /div button clickpause pause /button button clickresume resume /button /template在这个示例中回调通过解构拿到了delta距上一帧的毫秒数并将其渲染到页面上同时用一个 60 的fpsLimit限制了执行频率。三、回调参数delta 与 timestampuseRafFn的回调函数接收一个UseRafFnCallbackArguments类型的参数对象见 packages/core/useRafFn/index.tsexport interface UseRafFnCallbackArguments { /** * Time elapsed between this and the last frame. */ delta: number /** * Time elapsed since the creation of the web page. */ timestamp: DOMHighResTimeStamp }参数类型含义deltanumber本次回调与上一次回调之间的时间差毫秒可用于计算移动距离、速度等物理量timestampDOMHighResTimeStamp页面创建以来流逝的时间毫秒即浏览器传入requestAnimationFrame回调的高精度时间戳Time origin值得注意的实现细节是delta与浏览器原生requestAnimationFrame回调传入的时间戳并非同一个值。在 index.ts 的loop函数中VueUse 会记录上一次的帧时间戳previousFrameTimestamp再计算差值function loop(timestamp: DOMHighResTimeStamp) { if (!isActive.value || !window) return if (!previousFrameTimestamp) previousFrameTimestamp timestamp const delta timestamp - previousFrameTimestamp // ... 帧率限制判断 ... previousFrameTimestamp timestamp fn({ delta, timestamp }) // ... }也就是说第一帧执行时delta为 0因为没有前一帧从第二帧开始delta才反映真实的帧间隔。这一行为在 index.browser.test.ts 中通过fn.mock.calls[0][0]?.delta与fn.mock.calls[0][0]?.timestamp断言其存在性得到了验证。四、配置项详解useRafFn的第二个参数是UseRafFnOptions包含三个核心配置项export interface UseRafFnOptions extends ConfigurableWindow { immediate?: boolean // default true fpsLimit?: MaybeRefOrGetternumber | null // default null once?: boolean // default false }4.1 immediate是否立即开始默认值true即创建时立刻调用resume()开始循环见 index.ts。设为false时循环不会自动开始需要手动调用resume()启动。典型场景初始化数据尚未就绪或希望按用户交互时机再启动动画时使用immediate: false。测试用例 index.browser.test.ts 验证了immediate: false时isActive.value为false。4.2 fpsLimit帧率限制默认值null不限制跟随浏览器原生刷新率通常 60Hz 或 120Hz。传入数字 N 时回调每秒最多执行 N 次传入null则取消限制。类型为MaybeRefOrGetternumber | null意味着可以传入 ref 或 getter实现运行期动态调节帧率。底层实现index.ts将帧率换算为帧间隔阈值const intervalLimit computed(() { const limit toValue(fpsLimit) return limit ? 1000 / limit : null })随后在loop中若当前delta小于阈值intervalLimit.value delta intervalLimit.value则跳过本次回调、直接请求下一帧index.ts。注意这里的“跳过”指的是不执行回调而requestAnimationFrame本身仍按屏幕刷新率触发。测试用例验证了两个关键行为帧率 20 的回调调用次数少于帧率 60 的index.browser.test.ts传入响应式 ref 后动态把fr.value从 60 改为 20回调频率随之下降index.browser.test.ts。4.3 once只执行一次默认值false持续循环。设为true时回调执行一次后自动停止相当于“下一帧执行一次”。在 index.ts 中执行完回调后会将isActive置为false、rafId置空并直接返回不再请求下一帧。测试用例 index.browser.test.ts 明确断言once: true时回调恰好被调用 1 次而不设置时调用次数大于 1。4.4 window自定义 window 实例UseRafFnOptions继承自ConfigurableWindow见 packages/core/_configurable.ts因此还支持传入自定义的window实例例如在 iframe 或测试环境中使用。默认值defaultWindow在客户端为window、在服务端SSR为undefinedpackages/core/_configurable.ts。由于defaultWindow在服务端为undefineduseRafFn在 SSR 环境下不会启动循环、也不会报错天然具备服务端渲染安全性。五、返回对象Pausable 接口useRafFn返回一个Pausable类型对象接口定义见 packages/shared/utils/types.tsexport interface Pausable { readonly isActive: ReadonlyShallowRefboolean pause: Fn resume: Fn }成员类型说明isActiveReadonlyShallowRefboolean只读浅响应式标记true表示循环正在运行pauseFn暂停循环内部调用cancelAnimationFrame(rafId)并取消调度resumeFn恢复循环重置帧时间戳后重新发起requestAnimationFrame具体实现见 index.tsfunction resume() { if (!isActive.value window) { isActive.value true previousFrameTimestamp 0 rafId window.requestAnimationFrame(loop) } } function pause() { isActive.value false if (rafId ! null window) { window.cancelAnimationFrame(rafId) rafId null } } if (immediate) resume() tryOnScopeDispose(pause)几个重要的行为细节幂等性pause()在已暂停状态下重复调用是安全的isActive已为falserafId为null时不做任何事resume()也只在未激活时才会重新启动循环。自动清理通过tryOnScopeDispose(pause)当组件卸载或副作用作用域销毁时自动暂停循环避免内存泄漏与无效帧调用。这也是 VueUse 系列组合式函数的通用最佳实践。暂停后恢复会重置帧时间戳previousFrameTimestamp 0恢复后的第一帧delta会被重新初始化为 0避免将暂停时长计入帧间隔造成异常大的delta。isActive使用shallowRef存储、shallowReadonly暴露保证了状态读取的高效性与只读安全性。整个循环状态机在测试 index.browser.test.ts 中得到了全面覆盖暂停后isActive为false、恢复后为true且immediate: false时手动resume()同样能正常激活。六、源码级原理解读一帧的生命周期把上面各部分串起来useRafFn的完整运行流程如下初始化解构默认配置immediate true、fpsLimit null、window defaultWindow、once false创建isActive与intervalLimit计算属性。启动若immediate为真调用resume()——置isActive为true、清零previousFrameTimestamp、发起window.requestAnimationFrame(loop)。每帧回调loop若!isActive.value || !window直接返回防御性检查初始化首帧previousFrameTimestamp计算delta timestamp - previousFrameTimestamp若设置了fpsLimit且delta 1000 / fpsLimit则仅请求下一帧、不执行回调帧率限制逻辑否则更新previousFrameTimestamp、调用fn({ delta, timestamp })若once为真停止循环否则继续请求下一帧。暂停 / 清理pause()通过cancelAnimationFrame取消已排队的帧作用域销毁时tryOnScopeDispose(pause)兜底清理。整个实现不依赖 Vue 的响应式系统做调度仅用computed对fpsLimit做惰性求值配合toValue支持 ref/getter 输入因此运行开销极低非常适合高帧率场景。七、实战场景与最佳实践场景 1动画循环用fpsLimit将 CPU 密集型动画限制在 30 FPS兼顾流畅度与性能const { pause, resume } useRafFn(({ delta }) { progress.value Math.min(progress.value delta / 1000 * speed, 1) }, { fpsLimit: 30 })场景 2按需启动如进入视口才开启动画const { isActive, pause, resume } useRafFn(callback, { immediate: false }) onMounted(() resume()) onBeforeUnmount(() pause()) // 组件卸载时自动清理tryOnScopeDispose 已兜底场景 3下一帧执行一次防抖式延迟const { resume } useRafFn(updateLayout, { once: true, immediate: false }) // 需要时把更新推迟到下一帧合并同帧内的多次修改 resume()最佳实践建议优先用delta而非累加计数器基于delta计算位移/进度可避免因帧率波动或暂停导致的进度失真。不需要回调时及时pause()requestAnimationFrame循环在后台标签页会被浏览器自动降频但仍建议在隐藏或不再需要时主动暂停节省电量。善用fpsLimit的动态性传入 ref可在低电量模式、画质设置变化等场景下运行时降帧。SSR 环境下无需特判服务端defaultWindow为undefined循环不会启动代码天然可安全执行。八、总结useRafFn是一个轻量、可控、可组合的逐帧执行工具核心价值在于封装了requestAnimationFrame的递归调度与cancelAnimationFrame清理提供pause/resume/isActive完整的Pausable控制面通过fpsLimit支持响应式实现帧率限制通过once实现一次性执行回调携带delta与timestamp便于编写帧率无关的逻辑自动作用域清理 SSR 安全开箱即用。其 API 类型声明见 packages/core/useRafFn/index.ts使用文档见 packages/core/useRafFn/index.md交互示例见 packages/core/useRafFn/demo.vue行为验证见 packages/core/useRafFn/index.browser.test.ts。如果需要比“帧”更细粒度的定时控制还可以参考同仓库的useIntervalFn、useRafFn的姊妹工具useTimestamp等组合式函数。赞分享前端【免费下载链接】vueuseCollection of essential Vue Composition Utilities for Vue 3项目地址https://gitcode.com/gh_mirrors/vu/vueuse点击查看免费下载相关推荐VueUse useRafFn 完全指南基于 requestAnimationFrame 的响应式动画循环与 FPS 控制VueUse useRafFn 完全指南基于 requestAnimationFrame 的响应式动画循环与 FPS 控制 useRafFn 是 VueUse前端airi 项目实战深入掌握 VueUse useRafFn 与 requestAnimationFrame 驱动的动画循环控制airi 项目实战深入掌握 VueUse useRafFn 与 requestAnimationFrame 驱动的动画循环控制 useRafFn 是 VueUAI 应用人工智能大模型数字人AI Agent语音前端后端桌面应用移动开发即时通讯3D渲染vue-echarts 动画性能调优requestAnimationFrame 与帧优化vue echarts 动画性能调优requestAnimationFrame 与帧优化 在数据可视化项目中你是否遇到过图表动画卡顿、数据更新延迟的问题特前端图表库数据可视化上一篇UniversalUnityDemosaics终极指南3步实现Unity游戏马赛克高效移除下一篇本地多人游戏分屏工具完全指南用Nucleus Co-Op实现4人同屏游戏创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考