react-three-fiber中useFrame()内多模型动画不同步问题
解决模型旋转同步问题的方案
核心问题是所有模型共享了相同的旋转更新逻辑,要让每个模型旋转独立,需要给每个实例分配专属的旋转参数,并基于时间而非固定步长计算旋转角度。
具体实现步骤
- 给每个模型实例生成独立的旋转配置:在组件内部用
useRef存储每个模型独有的初始旋转角度、旋转速度,以及动画启动的时间偏移。 - 在
useFrame中基于时间计算旋转:用当前帧的时间减去模型的启动时间,结合专属速度计算旋转角度,避免固定步长导致的同步。 - 结合淡入淡出状态控制旋转:确保旋转只在模型可见阶段生效,或者根据动画进度调整旋转状态。
代码示例(TypeScript)
import { useRef, useState, useEffect } from 'react'; import { useFrame, useLoader } from '@react-three/fiber'; import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'; interface ModelFadeWrapperProps { modelPath: string; } export default function ModelFadeWrapper({ modelPath }: ModelFadeWrapperProps) { const gltf = useLoader(GLTFLoader, modelPath); const meshRef = useRef<THREE.Group>(null); // 存储当前模型的专属动画参数 const animParams = useRef({ startDelay: Math.random() * 3000, // 0-3秒随机延迟启动 rotationSpeed: (Math.random() - 0.5) * 0.02, // 随机旋转速度,正负方向都有 initialRotation: Math.random() * Math.PI * 2, // 初始旋转角度随机 startTime: 0, // 动画实际启动时间 isActive: false, // 是否处于可见状态 opacity: 0, }); const [isReady, setIsReady] = useState(false); useEffect(() => { const timer = setTimeout(() => { animParams.current.startTime = performance.now(); animParams.current.isActive = true; }, animParams.current.startDelay); // 这里可以添加循环动画的逻辑,比如淡出后重置位置和参数 return () => clearTimeout(timer); }, []); useFrame((state) => { if (!meshRef.current || !animParams.current.isActive) return; const elapsed = (state.clock.getElapsedTime() * 1000) - animParams.current.startTime; // 计算淡入淡出(示例逻辑,可根据需求调整) if (elapsed < 1000) { animParams.current.opacity = elapsed / 1000; } else if (elapsed > 4000) { animParams.current.opacity = 1 - (elapsed - 4000) / 1000; if (animParams.current.opacity <= 0) { animParams.current.isActive = false; // 重置位置和动画参数,准备下一次循环 animParams.current.startDelay = Math.random() * 3000; animParams.current.startTime = performance.now() + animParams.current.startDelay; animParams.current.initialRotation = Math.random() * Math.PI * 2; // 设置新的随机位置 meshRef.current.position.set( (Math.random() - 0.5) * 10, (Math.random() - 0.5) * 5, (Math.random() - 0.5) * 10 ); setTimeout(() => { animParams.current.isActive = true; }, animParams.current.startDelay); } } // 计算独立的旋转角度:初始角度 + 已过时间 * 旋转速度 meshRef.current.rotation.y = animParams.current.initialRotation + (elapsed / 1000) * animParams.current.rotationSpeed; // 设置透明度 meshRef.current.children.forEach(child => { if ('material' in child && child.material) { (child.material as THREE.MeshStandardMaterial).opacity = animParams.current.opacity; (child.material as THREE.MeshStandardMaterial).transparent = true; } }); }); useEffect(() => { if (gltf) { // 设置初始随机位置 meshRef.current?.position.set( (Math.random() - 0.5) * 10, (Math.random() - 0.5) * 5, (Math.random() - 0.5) * 10 ); setIsReady(true); } }, [gltf]); return isReady ? <primitive ref={meshRef} object={gltf.scene} /> : null; }
关键要点说明
- 专属参数存储:用
useRef保存每个模型的rotationSpeed、initialRotation、startDelay等参数,确保每个实例的值都是随机且独立的。 - 时间驱动的旋转计算:基于
state.clock.getElapsedTime()获取全局时间,减去模型自身的startTime得到该模型的已运行时间,再乘以专属速度计算旋转角度,彻底避免同步问题。 - 循环动画重置:当模型淡出后,重新生成随机参数和位置,设置新的启动延迟,实现无限循环的独立动画。
内容的提问来源于stack exchange,提问作者BillydogTheKid
相关产品推荐
相关产品推荐

