Files
antigravity-skills-reference/skills/spline-3d-integration/examples/interactive-scene.tsx

199 lines
5.8 KiB
TypeScript

// interactive-scene.tsx
// Full interactive Spline example — React
// Demonstrates: events, object manipulation, animation triggers, variable access
//
// Usage: drop this into a React/Next.js project and replace the scene URL
import { useRef, useState, useCallback } from 'react';
import Spline from '@splinetool/react-spline';
import type { Application } from '@splinetool/runtime';
const SCENE_URL = 'https://prod.spline.design/REPLACE_ME/scene.splinecode';
export default function InteractiveScene() {
const splineApp = useRef<Application>();
const [isLoaded, setIsLoaded] = useState(false);
const [lastEvent, setLastEvent] = useState<string>('');
// --- Called when scene finishes loading ---
function onLoad(app: Application) {
splineApp.current = app;
setIsLoaded(true);
console.log('Scene loaded');
}
// --- Listen to events from inside the Spline scene ---
function onMouseDown(e: any) {
setLastEvent(`mouseDown on: ${e.target?.name}`);
console.log('mouseDown event:', e.target);
}
function onMouseHover(e: any) {
setLastEvent(`mouseHover on: ${e.target?.name}`);
}
// --- Programmatically move an object ---
const moveObject = useCallback(() => {
if (!splineApp.current) return;
const obj = splineApp.current.findObjectByName('Cube');
if (!obj) return console.warn('Object "Cube" not found — check the name in Spline editor');
obj.position.x += 50; // move right
}, []);
// --- Trigger an animation event ---
const triggerAnimation = useCallback(() => {
if (!splineApp.current) return;
splineApp.current.emitEvent('mouseHover', 'Cube'); // triggers the mouseHover event on 'Cube'
}, []);
// --- Trigger animation in reverse (useful for toggle effects) ---
const reverseAnimation = useCallback(() => {
if (!splineApp.current) return;
splineApp.current.emitEventReverse('mouseHover', 'Cube');
}, []);
// --- Rotate object (RADIANS not degrees!) ---
const rotateObject = useCallback(() => {
if (!splineApp.current) return;
const obj = splineApp.current.findObjectByName('Cube');
if (!obj) return;
// 90 degrees = Math.PI / 2
obj.rotation.y += Math.PI / 2;
}, []);
// --- Change object scale ---
const scaleObject = useCallback((factor: number) => {
if (!splineApp.current) return;
const obj = splineApp.current.findObjectByName('Cube');
if (!obj) return;
obj.scale.x = factor;
obj.scale.y = factor;
obj.scale.z = factor;
}, []);
// --- Read/write Spline variables ---
const updateVariable = useCallback(() => {
if (!splineApp.current) return;
// Get a variable defined in the Spline editor
const score = splineApp.current.getVariable('score');
console.log('Current score variable:', score);
// Set a variable
splineApp.current.setVariable('score', (score as number) + 1);
}, []);
return (
<div style={{ position: 'relative', width: '100vw', height: '100vh' }}>
{/* Spline scene */}
<Spline
scene={SCENE_URL}
onLoad={onLoad}
onMouseDown={onMouseDown}
onMouseHover={onMouseHover}
style={{
position: 'absolute',
inset: 0,
width: '100%',
height: '100%',
zIndex: 0,
}}
/>
{/* Control panel — sits on top of the scene */}
{isLoaded && (
<div style={{
position: 'absolute',
bottom: 40,
left: '50%',
transform: 'translateX(-50%)',
zIndex: 10,
display: 'flex',
gap: 12,
flexWrap: 'wrap',
justifyContent: 'center',
background: 'rgba(0,0,0,0.6)',
padding: '16px 24px',
borderRadius: 12,
backdropFilter: 'blur(8px)',
}}>
<button onClick={moveObject}>Move Right</button>
<button onClick={rotateObject}>Rotate 90°</button>
<button onClick={() => scaleObject(2)}>Scale Up</button>
<button onClick={() => scaleObject(1)}>Reset Scale</button>
<button onClick={triggerAnimation}>Trigger Hover</button>
<button onClick={reverseAnimation}>Reverse Hover</button>
<button onClick={updateVariable}>Increment Variable</button>
</div>
)}
{/* Event log */}
{lastEvent && (
<div style={{
position: 'absolute',
top: 20,
left: '50%',
transform: 'translateX(-50%)',
zIndex: 10,
color: 'white',
background: 'rgba(0,0,0,0.5)',
padding: '8px 16px',
borderRadius: 8,
fontSize: 14,
fontFamily: 'monospace',
}}>
{lastEvent}
</div>
)}
{/* Loading state */}
{!isLoaded && (
<div style={{
position: 'absolute',
inset: 0,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
color: 'white',
fontSize: 18,
zIndex: 10,
background: '#0a0a0a',
}}>
Loading scene...
</div>
)}
</div>
);
}
/*
NOTES FOR AGENT:
1. Replace 'Cube' with the actual object name from the Spline editor
(right-click object in editor → "Copy Development Object ID" for the UUID,
or just use the display name shown in the Objects panel)
2. Rotation uses RADIANS:
90° = Math.PI / 2
180° = Math.PI
360° = Math.PI * 2
Convert: const toRad = (deg) => deg * (Math.PI / 180)
3. Variables must be defined in the Spline editor first
(right sidebar → Variables panel → add variable)
4. All available event types for onXxx props:
onMouseDown, onMouseUp, onMouseHover, onMousePress,
onKeyDown, onKeyUp, onStart, onScroll
5. If getting hydration errors in Next.js, wrap with:
const Spline = dynamic(() => import('@splinetool/react-spline/next'), { ssr: false })
*/