Reputation: 410
I am using useGLTF to load and show a sequence of models. It is working OK with Suspense/fallback, but I would like to improve it by showing the transition from one model to the next a little more elegantly.
With Suspense/fallback, the Canvas goes blank (and shows the fallback message) while the new model is being loaded. I've seen examples where startTransition allows the current model to continue showing until the next model is ready to render.
I think I am close with the code below, but perhaps need to do something different with the sequence of promises, or create an explicit promise somewhere. (This sample code simply loads a random new model on each click of the button.)
The code is also available in CodeSandbox
Any help or pointers to other examples would be greatly appreciated.
Bill
import { Suspense, useState, useEffect, useTransition } from "react";
import { Canvas } from "@react-three/fiber";
import { Html, OrbitControls, useGLTF } from "@react-three/drei";
export default function App() {
const [index, setIndex] = useState(0);
const [url, setUrl] = useState();
const [isPending, startTransition] = useTransition();
function ShowRandomClicked(e) {
setIndex(Math.floor(Math.random() * modelNames.length));
}
useEffect(() => {
const modelName = modelNames[index];
const urlGltf = `https://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Models/master/2.0/${modelName}/glTF/${modelName}.gltf`;
startTransition(() => setUrl(urlGltf));
}, [index, startTransition]);
useEffect(() => {}, [url]);
const Model = () => {
return (
<>
<primitive object={useGLTF(url).scene} scale={2} />
</>
);
};
return (
<div style={{ height: "800px" }}>
<button onClick={ShowRandomClicked}>Show a random model</button>
<Canvas>
<Suspense
fallback={
<Html>
<h1>should not see this fallback when using startTransition</h1>
</Html>
}
>
<Model />
</Suspense>
<ambientLight />
<OrbitControls />
</Canvas>
</div>
);
}
const modelNames = [
"Box",
"Duck",
"BrainStem",
"BarramundiFish",
"AntiqueCamera",
"CesiumMan",
"IridescenceSuzanne",
"DamagedHelmet",
"FlightHelmet",
"IridescenceLamp"
];
Upvotes: 0
Views: 1272
Reputation: 410
I believe it is possible to use Suspense/Fiber/Drei/startTransition to accomplish what I want, but I could not find a way to combine all of those things properly.
I reverted to direct threejs
and easily got exactly what I wanted using loadAsync
then()
and no Suspense.
I also needed to disable the UI while the load was happening, so I've added a blocking div. See in CodeSandbox.
import { useState, useEffect } from "react";
import { Canvas } from "@react-three/fiber";
import { OrbitControls } from "@react-three/drei";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
const gltfLoader = new GLTFLoader();
export default function App() {
const [index, setIndex] = useState(0);
const [gltf, setGltf] = useState();
const [isLoading, setIsLoading] = useState(false);
// load a random image from the list of images
function showRandomClicked(e) {
const i = Math.floor(Math.random() * modelNames.length);
setIndex(i);
console.log("click", i); // quick-click the button and watch the console to verify ui disabled
}
useEffect(() => {
setIsLoading(true);
const modelName = modelNames[index];
const urlGltf = `https://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Models/master/2.0/${modelName}/glTF/${modelName}.gltf`;
gltfLoader.loadAsync(urlGltf).then((g) => setGltf(g));
}, [index]);
useEffect(() => {
// post-process scene here if necessary
setIsLoading(false);
}, [gltf]);
return (
<div style={{ position: "relative", height: "600px" }}>
{/* create an overlay on the entire screen to block ui */}
<div
style={{
display: isLoading ? "block" : "none",
position: "absolute",
width: "100%",
height: "100%"
}}
/>
<button onClick={showRandomClicked}>Show a random model</button>
{/* blur filter to indicate that a new model is loading;
very elegant when the new model is a small change to the old model */}
<Canvas style={{ filter: `blur(${isLoading ? 2 : 0}px)` }}>
{gltf && <primitive object={gltf.scene} scale={3} />}
<ambientLight />
<OrbitControls />
</Canvas>
</div>
);
}
const modelNames = [
"Box",
"Duck",
"BrainStem",
"BarramundiFish",
"AntiqueCamera",
"CesiumMan",
"IridescenceSuzanne",
"DamagedHelmet",
"FlightHelmet",
"IridescenceLamp"
];
Upvotes: 0