React Three Fiber如何用tween/animejs基于缓动曲线实现相机动画
tween.js、anime.js这类缓动库完全可以用来实现R3F场景内的数值平滑过渡效果,你遇到的问题是引用管理和API调用参数错误导致的。
问题原因
- 你声明的
animValue是组件内部普通变量,组件每次重渲染都会被重新初始化为{value: 0},且useFrame的闭包捕获的是首次渲染的旧变量引用,导致动画修改的值和你读取的值不是同一个对象。 anime.tick()方法要求传入的是两帧之间的时间增量(单位毫秒),你传入的clock.elapsedTime是程序启动到当前的总时间,参数错误导致动画没有正常执行。- 你在
useFrame中用+=累加动画值,而anime生成的动画值本身是从0到10的插值结果,累加会导致最终位置远大于预期值。
方案1:修复现有anime.js实现
import { useRef, useEffect } from 'react' import { useFrame } from '@react-three/fiber' import anime from 'animejs' function Scene() { const animRef = useRef(null) // 用useRef存储动画目标对象,全程引用不变,不会被重渲染重置 const animValue = useRef({ value: 0 }) const cameraRef = useRef(null) useEffect(() => { animRef.current = anime({ targets: animValue.current, value: 10, easing: 'easeInOutQuad', autoplay: false, duration: 1000, // 补全动画时长,单位毫秒 }) }, []) // 空依赖数组,只初始化一次动画实例 useFrame(({ clock, camera }) => { if (animRef.current) { // 传入帧间隔时间,clock.getDelta()返回单位为秒,转成毫秒适配anime参数要求 animRef.current.tick(clock.getDelta() * 1000) // 直接赋值动画插值结果,不要累加 camera.position.z = animValue.current.value } }) return <perspectiveCamera ref={cameraRef} makeDefault /> }
方案2:R3F原生轻量实现(无第三方依赖)
如果只是简单的单段缓动动画,不需要引入额外的动画库,直接自己实现缓动逻辑性能更好,也不会有依赖兼容问题:
import { useRef } from 'react' import { useFrame } from '@react-three/fiber' // 提前定义easeInOutQuad缓动函数,输入0-1的原始进度,返回缓动后0-1的进度 const easeInOutQuad = (t) => t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2 function Scene() { const animStartTime = useRef(null) const ANIM_DURATION = 1000 // 动画总时长,单位毫秒 const START_Z = 0 // 相机初始z轴位置 const END_Z = 10 // 相机目标z轴位置 useFrame(({ clock, camera }) => { // 动画首次执行时记录开始时间 if (!animStartTime.current) { animStartTime.current = clock.elapsedTime } // 计算当前动画进度,限制在0-1区间避免超出目标值 const elapsedMs = (clock.elapsedTime - animStartTime.current) * 1000 const rawProgress = Math.min(elapsedMs / ANIM_DURATION, 1) // 应用缓动曲线 const easedProgress = easeInOutQuad(rawProgress) // 线性插值计算当前z轴位置 camera.position.z = START_Z + (END_Z - START_Z) * easedProgress }) return <perspectiveCamera makeDefault /> }
方案3:生态适配方案(复杂动画场景推荐)
如果需要暂停、回放、序列动画、状态联动等复杂控制能力,推荐使用R3F生态专用的动画库@react-spring/three,无需手动处理帧同步和引用管理:
import { useSpring, a } from '@react-spring/three' import { PerspectiveCamera } from '@react-three/drei' // 创建支持动画属性的相机组件 const AnimatedCamera = a(PerspectiveCamera) function Scene() { // 定义动画状态和配置 const [{ z }, api] = useSpring(() => ({ z: 0, config: { duration: 1000, easing: (t) => t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2 } })) // 需要触发动画时调用 api.start({ z: 10 }) 即可 // 反向播放直接调用 api.start({ z: 0 }) 即可 return <AnimatedCamera position-z={z} makeDefault /> }
内容的提问来源于stack exchange,提问作者H3lltronik
相关产品推荐
相关产品推荐

