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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 22:05:23