Search code examples
javascriptanimationthree.jsreact-three-fibergltf

Unexpected animation for gltf files with react-three


I'm using the react-three package to load gltf files to my react app. However, I noticed that after the app rendered, the 3d model is not autoplaying. At first I thought it's the animation issue but the weird thing is when I scrolled up or down, the model would move just one or two frames and it was very jumpy. Also, when I clicked on the model, it started to animate normally...until not.

I'm scratching my head here to figure out this weird behavior...Can anyone help me with this? Thanks!

DEMO: https://endearing-rugelach-2ca48b.netlify.app/ (The robot model and the floating balls below have the same issue)

Code for the robot model:

import React, { Suspense, useEffect, useState } 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");
  let mixer = new THREE.AnimationMixer(scene);
  animations.forEach((clip) => {
    const action = mixer.clipAction(clip);
    action.play();
  });
  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(() => {
    // Add a listener for changes to the screen size
    const mediaQuery = window.matchMedia("(max-width: 450px)");

    // Set the initial value of the `isMobile` state variable
    setIsMobile(mediaQuery.matches);

    // Define a callback function to handle changes to the media query
    const handleMediaQueryChange = (event) => {
      setIsMobile(event.matches);
    };

    // Add the callback function as a listener for changes to the media query
    mediaQuery.addEventListener("change", handleMediaQueryChange);

    // Remove the listener when the component is unmounted
    return () => {
      mediaQuery.removeEventListener("change", handleMediaQueryChange);
    };
  }, []);

  return (
    <Canvas
      frameloop="demand"
      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;


Solution

  • As @Don McCurdy mentioned in the comment, the problem is the line frameloop="demand. After removing it, the model works great. Hope this is helpful for developers who run into the same issue.