使用react-three加载GLTF文件时动画异常问题求助
解决React Three Fiber中GLTF模型动画无法自动播放的问题
问题根源
你给Canvas设置了frameloop="demand",这个模式下React Three Fiber(R3F)只会在用户交互(点击、滚动)或场景状态变化时才触发渲染帧更新。而动画依赖AnimationMixer通过useFrame持续获取delta时间来推进,没有持续的帧更新,动画自然卡住,只有滚动/点击时才会跳几帧。
修复方案
方案1:修改frameloop模式(推荐)
把Canvas的frameloop参数从"demand"改为"always",让R3F持续触发渲染帧,动画就能正常播放:
<Canvas frameloop="always" // 这里修改 shadows dpr={[1, 2]} camera={{ position: [20, 3, 5], fov: 25 }} gl={{ preserveDrawingBuffer: true }} > {/* 其他组件内容 */} </Canvas>
方案2:保留demand模式但手动触发渲染
如果必须保留demand模式的性能优化,可以在useFrame里手动请求渲染,强制每一帧更新动画:
useFrame((state, delta) => { mixer.update(delta); state.gl.setAnimationLoop(() => state.render()); // 手动触发渲染 });
注意:这个方法会抵消demand模式的性能优势,优先推荐方案1。
额外优化点
- 用
useRef保存mixer实例,避免组件重渲染时重复创建:
const mixer = useRef(new THREE.AnimationMixer(scene)).current;
原代码里直接用let mixer = new THREE.AnimationMixer(scene),每次组件渲染都会新建实例,可能导致动画异常。
- 用
useEffect初始化动画,确保组件挂载后只执行一次:
useEffect(() => { const actions = animations.map(clip => mixer.clipAction(clip)); actions.forEach(action => action.play()); return () => actions.forEach(action => action.stop()); }, [animations, mixer]);
修改后的完整Robot组件代码
import React, { Suspense, useEffect, useState, useRef } from "react"; import { Canvas, useFrame } from "@react-three/fiber"; import { OrbitControls, Preload, useGLTF } from "@react-three/drei"; import CanvasLoader from "../Loader"; import * as THREE from "three"; const Robot = ({ isMobile }) => { const { scene, animations } = useGLTF("./robot/scene.gltf"); const mixer = useRef(new THREE.AnimationMixer(scene)).current; useEffect(() => { const actions = animations.map(clip => mixer.clipAction(clip)); actions.forEach(action => action.play()); return () => actions.forEach(action => action.stop()); }, [animations, mixer]); useFrame((state, delta) => { mixer.update(delta); }); return ( <mesh> <hemisphereLight intensity={0.15} groundColor="black" /> <spotLight position={[-20, 50, 10]} angle={0.12} penumbra={1} intensity={1} castShadow shadow-mapSize={1024} /> <pointLight intensity={1} /> <primitive object={scene} dispose={null} scale={isMobile ? 2 : 1.6} position={isMobile ? [-10, -6, -2] : [-2, -4, 0]} rotation={isMobile ? [0, 1.5, 0] : [0, 1.3, 0]} /> </mesh> ); }; const RobotCanvas = () => { const [isMobile, setIsMobile] = useState(false); useEffect(() => { const mediaQuery = window.matchMedia("(max-width: 450px)"); setIsMobile(mediaQuery.matches); const handleMediaQueryChange = (event) => { setIsMobile(event.matches); }; mediaQuery.addEventListener("change", handleMediaQueryChange); return () => mediaQuery.removeEventListener("change", handleMediaQueryChange); }, []); return ( <Canvas frameloop="always" shadows dpr={[1, 2]} camera={{ position: [20, 3, 5], fov: 25 }} gl={{ preserveDrawingBuffer: true }} > <Suspense fallback={<CanvasLoader />}> <OrbitControls enableZoom={false} maxPolarAngle={Math.PI / 2} minPolarAngle={Math.PI / 2} /> <Robot isMobile={isMobile} /> </Suspense> <Preload all /> </Canvas> ); }; export default RobotCanvas;
内容的提问来源于stack exchange,提问作者Jadeyyy
相关产品推荐
相关产品推荐

