使用react-three-fiber实现相机平滑动画问题求助
解决相机平滑过渡到指定Mesh的问题
直接瞬移是因为没有对相机的位置/旋转做插值动画,下面给你两种可行的实现方案:
方案一:用@react-spring/three实现弹簧动画
这是React生态里做3D动画最省心的方式,适配react-three-fiber:
- 确保安装依赖:
npm install @react-spring/three
- 修改相机组件为动画版,绑定弹簧状态:
import { useSpring, animated } from '@react-spring/three' import { PerspectiveCamera } from '@react-three/fiber' // 在组件内部 const [{ position, rotation }, api] = useSpring(() => ({ position: [0, 0, 5], // 相机初始位置 rotation: [0, 0, 0], config: { duration: 1200 } // 动画时长,可按需调整 })) // 把普通相机换成animated包装的版本 <animated.perspectiveCamera ref={cameraRef} position={position} rotation={rotation} fov={75} aspect={window.innerWidth / window.innerHeight} near={0.1} far={1000} />
- 找到目标Mesh后触发动画:
const navigateToMesh = (targetMesh) => { // 计算相机目标位置(比如在Mesh后方3个单位,避免贴脸) const targetPosition = targetMesh.position.clone() targetPosition.z += 3 // 启动弹簧动画 api.start({ position: targetPosition.toArray(), // 如果需要看向Mesh,也可以给rotation加动画,或者用lookAt结合动画 // rotation: targetMesh.rotation.toArray() }) }
如果需要相机始终看向Mesh,可以在动画里同步更新lookAt,或者用useSpring的onFrame回调实时设置:
api.start({ position: targetPosition.toArray(), onFrame: () => { cameraRef.current.lookAt(targetMesh.position) } })
方案二:手动实现缓动动画(无额外依赖)
如果不想加库,用原生requestAnimationFrame做缓动:
- 保存必要的引用:
import { useRef, useEffect } from 'react' import * as THREE from 'three' const cameraRef = useRef(null) const targetPosRef = useRef(null) const isAnimating = useRef(false)
- 启动动画循环:
useEffect(() => { const animateCamera = () => { if (isAnimating.current && targetPosRef.current) { const cam = cameraRef.current const target = targetPosRef.current // 缓动系数,0~1之间,越小动画越慢 const easeFactor = 0.06 // 逐帧更新相机位置 cam.position.x += (target.x - cam.position.x) * easeFactor cam.position.y += (target.y - cam.position.y) * easeFactor cam.position.z += (target.z - cam.position.z) * easeFactor // 当相机距离目标足够近时停止动画 if (cam.position.distanceTo(target) < 0.02) { isAnimating.current = false cam.position.copy(target) // 确保最终位置精准 } // 让相机始终看向目标Mesh cam.lookAt(targetMeshRef.current.position) } requestAnimationFrame(animateCamera) } const frameId = requestAnimationFrame(animateCamera) return () => cancelAnimationFrame(frameId) }, [])
- 触发动画:
const goToTargetMesh = (meshId) => { const targetMesh = sceneRef.current.getObjectById(meshId) if (!targetMesh) return // 设置相机目标位置 targetPosRef.current = targetMesh.position.clone().add(new THREE.Vector3(0, 0, 3)) isAnimating.current = true }
注意事项
- 如果用了OrbitControls,动画期间可以暂时禁用控制器,避免冲突:
const controlsRef = useRef(null) // 启动动画时 controlsRef.current.enabled = false // 动画结束后恢复 controlsRef.current.enabled = true
内容的提问来源于stack exchange,提问作者Cyrus Zei
相关产品推荐
相关产品推荐

