import * as THREE from 'three'; import { useRef, useState, useEffect } from 'react'; import { Canvas, useFrame } from '@react-three/fiber'; import { OrbitControls, Environment, MeshReflectorMaterial, ContactShadows } from '@react-three/drei'; import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'; import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js'; const modelPath = './inflation.glb'; function Model({ ...props }) { const [model, setModel] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const mixerRef = useRef(null); const actionsRef = useRef([]); const handleClick = () => { actionsRef.current.forEach((action) => { action.reset(); action.play(); }); }; const onPointerOver = () => { document.body.style.cursor = 'pointer'; }; const onPointerOut = () => { document.body.style.cursor = 'auto'; }; useEffect(() => { const loader = new GLTFLoader(); const dracoLoader = new DRACOLoader(); dracoLoader.setDecoderPath('https://www.gstatic.com/draco/v1/decoders/'); loader.setDRACOLoader(dracoLoader); loader.load( modelPath, (gltf) => { const mesh = gltf.scene; const mixer = new THREE.AnimationMixer(mesh); mixerRef.current = mixer; if (gltf.animations && gltf.animations.length) { gltf.animations.forEach((clip) => { clip.duration = 6; const action = mixer.clipAction(clip); action.clampWhenFinished = true; action.loop = THREE.LoopOnce; action.setDuration(6); action.reset(); action.play(); actionsRef.current.push(action); }); } setModel(mesh); setLoading(false); // ✅ Clear loading attributes once done document.body.removeAttribute('data-loading'); document.body.classList.remove('loading'); }, (xhr) => { if (xhr.total) { const percent = Math.round((xhr.loaded / xhr.total) * 100); // ✅ Update body attribute while loading document.body.setAttribute('data-loading', percent.toString()); } }, (err) => { console.error('An error happened loading the model:', err); setError(err); setLoading(false); // also clear so UI doesn’t get stuck document.body.removeAttribute('data-loading'); document.body.classList.remove('loading'); } ); return () => { if (mixerRef.current) { mixerRef.current.stopAllAction(); } }; }, []); useFrame((_, delta) => { if (mixerRef.current) { mixerRef.current.update(delta); } }); if (loading || error || !model) { return null; } return ( ); } function MetalGround({ ...props }) { return ( ); } export default function App() { return (
); }