You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React Three Fiber如何用tween/animejs基于缓动曲线实现相机动画

tween.js、anime.js这类缓动库完全可以用来实现R3F场景内的数值平滑过渡效果,你遇到的问题是引用管理和API调用参数错误导致的。

问题原因
  1. 你声明的animValue是组件内部普通变量,组件每次重渲染都会被重新初始化为{value: 0},且useFrame的闭包捕获的是首次渲染的旧变量引用,导致动画修改的值和你读取的值不是同一个对象。
  2. anime.tick()方法要求传入的是两帧之间的时间增量(单位毫秒),你传入的clock.elapsedTime是程序启动到当前的总时间,参数错误导致动画没有正常执行。
  3. 你在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.28 01:06:08