React中React Three Fiber与Three.js动画无法正常播放问题
React中GLTF模型动画闪回从头开始的解决方案
核心问题
你的代码中AnimationMixer是组件函数内部的局部变量,组件每次重渲染时都会创建新的mixer实例,导致动画被反复重置到初始状态。另外,手动使用GLTFLoader不如drei库的useGLTF高效,后者自带缓存机制,能避免重复加载。
修复方案
1. 用useRef持久化AnimationMixer
将mixer存储在useRef中,确保组件重渲染时不会重新初始化,保持动画状态的连续性。
2. 改用drei的useGLTF加载模型
useGLTF是react-three-fiber生态中专门封装的GLTF加载钩子,简化加载流程且自带缓存,更符合R3F的使用习惯。
3. 可选:用useAnimations简化动画管理
drei的useAnimations可以自动处理mixer和动画动作,减少手动代码量。
修复后的完整代码
import React, { Suspense, useRef } from "react"; import { Canvas, useFrame } from "@react-three/fiber"; import { OrbitControls, useGLTF, useAnimations } from "@react-three/drei"; import * as THREE from "three"; import CanvasLoader from "../Loader"; const Earth = (props) => { const model = useGLTF("./a_windy_day/scene.gltf"); // 用useRef持久化AnimationMixer实例 const mixerRef = useRef(null); // 初始化动画(仅执行一次) if (!mixerRef.current && model.animations.length) { mixerRef.current = new THREE.AnimationMixer(model.scene); model.animations.forEach(clip => { const action = mixerRef.current.clipAction(clip); action.play(); // 若需循环播放,可添加以下配置 // action.setLoop(THREE.LoopRepeat); // action.clampWhenFinished = false; }); } // 每一帧更新动画 useFrame((_, delta) => { mixerRef.current?.update(delta); }); // 处理模型材质和阴影 model.scene.traverse(child => { if (child.isMesh) { child.castShadow = false; child.receiveShadow = false; child.material.side = THREE.FrontSide; } }); return <primitive object={model.scene} scale={props.scale || 1} />; }; // --- 可选:使用useAnimations的简化版本 --- // const Earth = (props) => { // const model = useGLTF("./a_windy_day/scene.gltf"); // const { actions } = useAnimations(model.animations, model.scene); // // 播放所有动画(或指定单个动画,如actions["你的动画名称"].play()) // React.useEffect(() => { // Object.values(actions).forEach(action => { // action.play(); // action.setLoop(THREE.LoopRepeat); // }); // }, [actions]); // model.scene.traverse(child => { // if (child.isMesh) { // child.castShadow = false; // child.receiveShadow = false; // child.material.side = THREE.FrontSide; // } // }); // return <primitive object={model.scene} scale={props.scale || 1} />; // }; const EarthCanvas = () => { return ( <Canvas> <Suspense fallback={<CanvasLoader />}> <Earth /> <OrbitControls /> {/* 添加基础光照,提升模型显示效果 */} <ambientLight intensity={0.6} /> <directionalLight position={[3, 3, 3]} intensity={1.2} /> </Suspense> </Canvas> ); }; export default EarthCanvas;
额外检查项
- 确认模型动画的循环设置:部分GLTF模型默认未开启循环,需手动通过
action.setLoop(THREE.LoopRepeat)配置。 - 验证模型路径:确保
./a_windy_day/scene.gltf存放在项目的public目录下,或配置了正确的静态资源访问路径。 - 避免不必要的组件重渲染:若
Earth组件接收的props频繁变化,可使用useMemo包裹props,防止组件重复初始化。
内容的提问来源于stack exchange,提问作者AKzar1el
相关产品推荐
相关产品推荐

