Spaces:
Sleeping
Sleeping
File size: 4,657 Bytes
5008b66 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 |
import { Suspense, useEffect, useRef, useState } from 'react';
import Spline from '@splinetool/react-spline';
interface SplineBackgroundProps {
scene?: string;
className?: string;
onLoad?: () => void;
}
// Fallback component for loading state
function SplineFallback({ message = 'Loading 3D Scene...' }: { message?: string }) {
return (
<div className="w-full h-full bg-gradient-surface flex items-center justify-center">
<div className="animate-pulse space-y-4">
<div className="w-32 h-32 bg-primary/20 rounded-full mx-auto animate-float"></div>
<div className="text-center text-muted-foreground">{message}</div>
</div>
</div>
);
}
export default function SplineBackground({
scene = "https://prod.spline.design/6Wq8T6XmHxrGMwni/scene.splinecode",
className = "",
onLoad
}: SplineBackgroundProps) {
const [error, setError] = useState<string | null>(null);
const [ready, setReady] = useState(false);
const containerRef = useRef<HTMLDivElement | null>(null);
const isSplineCode = scene.trim().toLowerCase().endsWith('.splinecode');
// Preflight check: ensure the scene URL is reachable before trying to load Spline
useEffect(() => {
let cancelled = false;
setError(null);
setReady(false);
(async () => {
try {
// Use no-cors GET: many CDNs block HEAD and CORS; opaque means we cannot inspect but still OK to try
const res = await fetch(scene, { method: 'GET', mode: 'no-cors' });
if (!cancelled) setReady(true);
} catch (e) {
console.error('Spline scene pre-check failed', e);
if (!cancelled) setError('Failed to reach 3D scene');
}
})();
return () => { cancelled = true; };
}, [scene]);
// Handle WebGL context loss gracefully
useEffect(() => {
const el = containerRef.current;
if (!el) return;
const onContextLost = (ev: Event) => {
console.warn('WebGL context lost');
setError('Graphics context lost');
ev.preventDefault();
};
const onContextRestored = () => {
console.info('WebGL context restored');
setError(null);
};
el.addEventListener('webglcontextlost', onContextLost as EventListener, false);
el.addEventListener('webglcontextrestored', onContextRestored as EventListener, false);
return () => {
el.removeEventListener('webglcontextlost', onContextLost as EventListener, false);
el.removeEventListener('webglcontextrestored', onContextRestored as EventListener, false);
};
}, []);
if (error) {
return (
<div ref={containerRef} className={`w-full h-full ${className}`}>
<SplineFallback message={error} />
</div>
);
}
// If the provided URL is not a .splinecode, use an iframe fallback. This supports community/share links.
if (!isSplineCode) {
return (
<div ref={containerRef} className={`w-full h-full ${className}`}>
{ready ? (
<iframe
src={scene}
title="Spline Scene"
loading="lazy"
referrerPolicy="no-referrer"
style={{ width: '100%', height: '100%', border: 'none', background: 'transparent' }}
allow="xr-spatial-tracking; clipboard-read; clipboard-write; accelerometer; magnetometer; gyroscope; autoplay"
/>
) : (
<SplineFallback />
)}
</div>
);
}
return (
<div ref={containerRef} className={`w-full h-full ${className}`}>
<Suspense fallback={<SplineFallback />}>
{ready ? (
<Spline
scene={scene}
onLoad={onLoad}
// If the version supports it, onError will be called; otherwise pre-check and boundary handle it.
// @ts-expect-error - onError may not exist on older versions
onError={(e: unknown) => {
console.error('Spline load error', e);
setError('Failed to load 3D scene');
}}
style={{ width: '100%', height: '100%', background: 'transparent' }}
/>
) : (
<SplineFallback />
)}
</Suspense>
</div>
);
}
// Alternative Spline scenes for different purposes
export const SplineScenes = {
// Abstract floating elements
abstract: "https://prod.spline.design/6Wq8T6XmHxrGMwni/scene.splinecode",
// Microphone visualization
microphone: "https://prod.spline.design/kR3K8EqaAlKZXBPv/scene.splinecode",
// Speaker/audio visualization
speaker: "https://prod.spline.design/m8rFoLJGtKVQZuE5/scene.splinecode",
// Particle system
particles: "https://prod.spline.design/K7n4XGsOzMqBgEF1/scene.splinecode"
}; |