diff --git a/src/components/scene/Scene.tsx b/src/components/scene/Scene.tsx index 637afed..e3fdc84 100644 --- a/src/components/scene/Scene.tsx +++ b/src/components/scene/Scene.tsx @@ -8,7 +8,6 @@ import { Text, Html, type PerspectiveCameraProps, - useProgress, } from "@react-three/drei"; import { animated as threeAnimated, @@ -45,6 +44,7 @@ import { Credits } from "../ui/Credits"; import { isWebGL2Available } from "@react-three/drei"; import { useWindowSize } from "../hooks/useWindowSize"; import { Info } from "../util/Info"; +import { Loader } from "../ui/Loader"; const AnimatedCam = threeAnimated(PerspectiveCamera); const AnimatedButton = webAnimated.button as React.ComponentType< @@ -75,7 +75,6 @@ function FpsUpdater({ export function Scene(props: JSX.IntrinsicElements["group"]) { const { nodes, materials } = useGLTF("/scene.glb") as unknown as GLTFResult; - const { progress } = useProgress(); const { width, height } = useWindowSize(); const config = useConfig(); @@ -148,6 +147,7 @@ export function Scene(props: JSX.IntrinsicElements["group"]) { // Scene return (
+ {isWebGL2Available() ? ( width >= 1.5 * height ? ( <> @@ -178,30 +178,14 @@ export function Scene(props: JSX.IntrinsicElements["group"]) {
- {isNaN(fps) ? "-" : fps.toFixed(0)} fps + {fps > 0 && <>{fps.toFixed(0)} fps}
- -
-

Loading...

-
-
-
-
- - } - > + diff --git a/src/components/ui/Loader.tsx b/src/components/ui/Loader.tsx new file mode 100644 index 0000000..8fe4389 --- /dev/null +++ b/src/components/ui/Loader.tsx @@ -0,0 +1,91 @@ +"use client"; + +import { useProgress } from "@react-three/drei"; +import { animated, useSpring, useTransition } from "@react-spring/web"; +import { useEffect, useState, useRef } from "react"; +import { createPortal } from "react-dom"; + +export const Loader = ({ minDuration = 750, fadeMs = 600 }) => { + const { progress } = useProgress(); + const [loadedAt, setLoadedAt] = useState(null); + + const maxSeen = useRef(0); + if (progress > maxSeen.current) maxSeen.current = progress; + + useEffect(() => { + if (maxSeen.current === 100 && loadedAt === null) setLoadedAt(Date.now()); + }, [loadedAt]); + + const visible = + loadedAt === null || Date.now() - loadedAt < minDuration + fadeMs; + + const barSpring = useSpring({ + from: { width: "0%" }, + to: { width: "100%" }, + config: { duration: minDuration }, + }); + + const overlay = useTransition(visible, { + from: { opacity: 1 }, + enter: { opacity: 1 }, + leave: { opacity: 0 }, + config: { duration: fadeMs, easing: (t) => Math.pow(t, 2) }, + }); + + return createPortal( + overlay( + (styles, show) => + show && ( + // @ts-expect-error children not typed bug + +

Loading…

+ +
+ {/* @ts-expect-error children not typed bug */} + + {/* @ts-expect-error children not typed bug */} + + {barSpring.width.to((w) => { + const p = parseInt(w) || 0; + return `${p > 5 ? p : ""}`; + })} + + +
+
+ ) + ), + document.body + ); +};