assets/README.md
# Spline Interactive - Assets
This directory contains starter templates and examples for Spline + React integration.
## Using the Project Generator
The easiest way to get started is using the `project_generator.py` script:
```bash
# Interactive mode
./scripts/project_generator.py
# CLI mode
./scripts/project_generator.py --name my-spline-app
# Next.js project
./scripts/project_generator.py --name my-spline-app --nextjs
```
This automatically generates a complete project with:
- package.json with all dependencies
- Vite or Next.js configuration
- React components with Spline integration
- Example event handlers
- Starter CSS
- README with setup instructions
## Project Structure
Generated projects have this structure:
```
my-spline-app/
├── package.json
├── vite.config.js (or next.config.js)
├── index.html (Vite only)
├── README.md
├── src/
│ ├── main.jsx (Vite only)
│ ├── App.jsx
│ └── App.css
└── app/ (Next.js only)
└── page.jsx
```
## Component Templates
Use `component_builder.py` to generate reusable Spline components:
```bash
# Interactive mode
./scripts/component_builder.py
# Generate specific component
./scripts/component_builder.py --name ProductViewer --type interactive
# Save to file
./scripts/component_builder.py --name ProductViewer --type interactive --output src/components/ProductViewer.jsx
```
### Available Component Types
1. **basic** - Simple Spline scene wrapper
2. **interactive** - With click/hover event handling
3. **animated** - With animation trigger methods
4. **controlled** - Exposes methods via ref for parent control
5. **responsive** - Adapts to mobile/desktop screen sizes
6. **lazy** - Lazy-loaded with React.lazy()
## Example Integrations
### With GSAP ScrollTrigger
```jsx
import { useRef, useEffect } from 'react';
import Spline from '@splinetool/react-spline';
import gsap from 'gsap';
import ScrollTrigger from 'gsap/ScrollTrigger';
gsap.registerPlugin(ScrollTrigger);
export default function ScrollSpline() {
const splineApp = useRef();
function onLoad(app) {
splineApp.current = app;
ScrollTrigger.create({
trigger: '.scene',
start: 'top center',
onEnter: () => app.emitEvent('mouseHover', 'Product')
});
}
return (
<div className="scene">
<Spline
scene="https://prod.spline.design/YOUR-SCENE-ID/scene.splinecode"
onLoad={onLoad}
/>
</div>
);
}
```
### With Framer Motion
```jsx
import { motion } from 'framer-motion';
import Spline from '@splinetool/react-spline';
export default function AnimatedSpline() {
return (
<motion.div
initial={{ opacity: 0, scale: 0.8 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ duration: 0.8 }}
style={{ width: '100%', height: '600px' }}
>
<Spline scene="https://prod.spline.design/YOUR-SCENE-ID/scene.splinecode" />
</motion.div>
);
}
```
### Product Configurator
```jsx
import { useRef, useState } from 'react';
import Spline from '@splinetool/react-spline';
export default function ProductConfigurator() {
const product = useRef();
const splineApp = useRef();
const [color, setColor] = useState('#ff6b6b');
function onLoad(spline) {
splineApp.current = spline;
product.current = spline.findObjectByName('Product');
}
function changeColor(newColor) {
setColor(newColor);
if (product.current) {
product.current.material.color.set(parseInt(newColor.substring(1), 16));
}
}
function rotate() {
if (product.current) {
product.current.rotation.y += Math.PI / 4;
}
}
return (
<div>
<Spline
scene="https://prod.spline.design/YOUR-SCENE-ID/scene.splinecode"
onLoad={onLoad}
/>
<div className="controls">
<button onClick={() => changeColor('#ff6b6b')}>Red</button>
<button onClick={() => changeColor('#4ecdc4')}>Cyan</button>
<button onClick={() => changeColor('#ffe66d')}>Yellow</button>
<button onClick={rotate}>Rotate</button>
</div>
</div>
);
}
```
### Multi-Scene Viewer
```jsx
import { useState } from 'react';
import Spline from '@splinetool/react-spline';
const scenes = {
scene1: 'https://prod.spline.design/SCENE-1/scene.splinecode',
scene2: 'https://prod.spline.design/SCENE-2/scene.splinecode',
scene3: 'https://prod.spline.design/SCENE-3/scene.splinecode'
};
export default function SceneSwitcher() {
const [currentScene, setCurrentScene] = useState('scene1');
return (
<div>
<Spline scene={scenes[currentScene]} />
<div className="tabs">
<button onClick={() => setCurrentScene('scene1')}>Scene 1</button>
<button onClick={() => setCurrentScene('scene2')}>Scene 2</button>
<button onClick={() => setCurrentScene('scene3')}>Scene 3</button>
</div>
</div>
);
}
```
## Getting Your Spline Scene URL
1. Open your scene in Spline editor (https://app.spline.design)
2. Click the "Export" button (top right)
3. Select "Code Export" → "React"
4. Copy the scene URL (format: `https://prod.spline.design/[ID]/scene.splinecode`)
5. Replace `YOUR-SCENE-ID` in the templates with your actual scene URL
## Common Use Cases
### Marketing Hero Section
```jsx
import Spline from '@splinetool/react-spline';
import './Hero.css';
export default function Hero() {
return (
<section className="hero">
<div className="hero-content">
<h1>Welcome to Our Product</h1>
<p>Experience 3D interaction</p>
<button>Get Started</button>
</div>
<div className="hero-scene">
<Spline scene="https://prod.spline.design/YOUR-SCENE-ID/scene.splinecode" />
</div>
</section>
);
}
```
### Interactive Portfolio
```jsx
import { useRef } from 'react';
import Spline from '@splinetool/react-spline';
export default function Portfolio() {
const splineApp = useRef();
function showProject(projectName) {
splineApp.current?.emitEvent('mouseHover', projectName);
}
return (
<div className="portfolio">
<nav>
<button onClick={() => showProject('Project1')}>Project 1</button>
<button onClick={() => showProject('Project2')}>Project 2</button>
<button onClick={() => showProject('Project3')}>Project 3</button>
</nav>
<Spline
scene="https://prod.spline.design/YOUR-SCENE-ID/scene.splinecode"
onLoad={(app) => (splineApp.current = app)}
/>
</div>
);
}
```
## Performance Tips
1. **Use On-Demand Rendering** (enabled by default):
```jsx
<Spline scene={sceneUrl} renderOnDemand={true} />
```
2. **Lazy Load Heavy Scenes**:
```jsx
const Spline = React.lazy(() => import('@splinetool/react-spline'));
```
3. **Optimize in Spline Editor**:
- Reduce polygon count
- Compress textures
- Limit lights to 2-3
- Use simple materials
4. **Create Mobile Versions**:
- Lower detail meshes
- Smaller textures (512x512)
- No shadows/reflections
## Troubleshooting
### Scene Not Loading
- Verify scene URL is complete and correct
- Check scene is published in Spline editor
- Look for CORS errors in browser console
- Ensure both `@splinetool/react-spline` and `@splinetool/runtime` are installed
### Events Not Working
- Use `onSplineMouseDown` not `onMouseDown`
- Verify objects have events configured in Spline editor
- Check object names match exactly (case-sensitive)
### Performance Issues
- Enable renderOnDemand
- Create mobile-specific scenes with lower detail
- Use lazy loading for heavy scenes
- Profile with Chrome DevTools
## Additional Resources
- [Spline Documentation](https://docs.spline.design)
- [React Spline GitHub](https://github.com/splinetool/react-spline)
- [Spline Community](https://spline.community)
- [Spline Tutorials](https://spline.design/tutorials)
references/api_reference.md
# Spline React API Reference
Complete reference for `@splinetool/react-spline` and Spline Application API.
## Installation
```bash
npm install @splinetool/react-spline @splinetool/runtime
# or
yarn add @splinetool/react-spline @splinetool/runtime
```
## Spline Component
### Import
```jsx
// Standard React
import Spline from '@splinetool/react-spline';
// Next.js (with SSR support)
import Spline from '@splinetool/react-spline/next';
```
### Props
#### `scene` (required)
- **Type**: `string`
- **Description**: URL to the Spline scene file
- **Example**: `"https://prod.spline.design/6Wq1Q7YGyM-iab9i/scene.splinecode"`
#### `onLoad`
- **Type**: `(spline: Application) => void`
- **Description**: Called once the scene has loaded. Provides Spline Application instance
- **Example**:
```jsx
function onLoad(spline) {
console.log('Scene loaded', spline);
const obj = spline.findObjectByName('Cube');
}
<Spline scene={sceneUrl} onLoad={onLoad} />
```
#### `renderOnDemand`
- **Type**: `boolean`
- **Default**: `true`
- **Description**: Enable on-demand rendering (only renders when scene changes)
- **Example**: `<Spline scene={sceneUrl} renderOnDemand={false} />`
#### `className`
- **Type**: `string`
- **Description**: CSS class name(s) for the canvas container
- **Example**: `<Spline scene={sceneUrl} className="my-scene" />`
#### `style`
- **Type**: `React.CSSProperties`
- **Description**: Inline CSS styles for the canvas container
- **Example**: `<Spline scene={sceneUrl} style={{ width: '100%', height: '600px' }} />`
#### `id`
- **Type**: `string`
- **Description**: HTML id attribute for the canvas element
- **Example**: `<Spline scene={sceneUrl} id="main-scene" />`
#### `ref`
- **Type**: `React.Ref<HTMLDivElement>`
- **Description**: React ref pointing to the container div element
- **Example**:
```jsx
const containerRef = useRef();
<Spline scene={sceneUrl} ref={containerRef} />
```
### Event Handler Props
#### `onSplineMouseDown`
- **Type**: `(e: SplineEvent) => void`
- **Description**: Fired when mouse button is pressed on an object
- **Event Object**:
- `e.target` - The object that was clicked (contains name, id, position, rotation, scale)
- `e.target.name` - Object name from Spline editor
- `e.target.id` - Object UUID
- **Example**:
```jsx
function onSplineMouseDown(e) {
console.log('Clicked:', e.target.name);
console.log('Position:', e.target.position);
}
```
#### `onSplineMouseUp`
- **Type**: `(e: SplineEvent) => void`
- **Description**: Fired when mouse button is released
- **Example**:
```jsx
function onSplineMouseUp(e) {
console.log('Released:', e.target.name);
}
```
#### `onSplineMouseHover`
- **Type**: `(e: SplineEvent) => void`
- **Description**: Fired when mouse hovers over an object
- **Example**:
```jsx
function onSplineMouseHover(e) {
console.log('Hovering:', e.target.name);
}
```
#### `onSplineKeyDown`
- **Type**: `(e: SplineEvent) => void`
- **Description**: Fired when a keyboard key is pressed
- **Example**:
```jsx
function onSplineKeyDown(e) {
console.log('Key pressed:', e.key);
}
```
#### `onSplineKeyUp`
- **Type**: `(e: SplineEvent) => void`
- **Description**: Fired when a keyboard key is released
#### `onSplineStart`
- **Type**: `(e: SplineEvent) => void`
- **Description**: Fired when the scene starts (after loading)
#### `onSplineLookAt`
- **Type**: `(e: SplineEvent) => void`
- **Description**: Fired when a camera look-at event occurs
#### `onSplineFollow`
- **Type**: `(e: SplineEvent) => void`
- **Description**: Fired when a camera follow event occurs
#### `onSplineScroll`
- **Type**: `(e: SplineEvent) => void`
- **Description**: Fired when a scroll event occurs in the scene
## Spline Application API
The `Application` object is provided via the `onLoad` callback.
### Methods
#### `emitEvent(eventName, nameOrUuid)`
- **Description**: Triggers a Spline event on an object
- **Parameters**:
- `eventName` (SplineEventName) - The event to trigger
- `nameOrUuid` (string) - Object name or UUID
- **Returns**: `void`
- **Example**:
```jsx
spline.emitEvent('mouseHover', 'Button');
spline.emitEvent('mouseDown', '8E8C2DDD-18B6-4C54-861D-7ED2519DE20E');
```
#### `emitEventReverse(eventName, nameOrUuid)`
- **Description**: Triggers a Spline event in reverse (from last state to first)
- **Parameters**:
- `eventName` (SplineEventName) - The event to trigger
- `nameOrUuid` (string) - Object name or UUID
- **Returns**: `void`
- **Example**:
```jsx
spline.emitEventReverse('mouseHover', 'Card');
```
#### `findObjectById(uuid)`
- **Description**: Finds an object by its UUID
- **Parameters**:
- `uuid` (string) - Object UUID from Spline editor
- **Returns**: `SPEObject` - The object if found, undefined otherwise
- **Example**:
```jsx
const obj = spline.findObjectById('8E8C2DDD-18B6-4C54-861D-7ED2519DE20E');
console.log(obj.name, obj.position);
```
#### `findObjectByName(name)`
- **Description**: Finds the first object matching the specified name
- **Parameters**:
- `name` (string) - Object name from Spline editor
- **Returns**: `SPEObject` - The object if found, undefined otherwise
- **Example**:
```jsx
const cube = spline.findObjectByName('Cube');
cube.position.x += 10;
```
#### `setZoom(zoom)`
- **Description**: Sets the camera zoom level
- **Parameters**:
- `zoom` (number) - Zoom value (1.0 is default, <1.0 zooms out, >1.0 zooms in)
- **Returns**: `void`
- **Example**:
```jsx
spline.setZoom(1.5); // Zoom in
spline.setZoom(0.7); // Zoom out
```
## SplineEventName Type
Available event types for `emitEvent()` and `emitEventReverse()`:
- `'mouseDown'` - Mouse button pressed
- `'mouseHover'` - Mouse hover
- `'mouseUp'` - Mouse button released
- `'keyDown'` - Keyboard key pressed
- `'keyUp'` - Keyboard key released
- `'start'` - Scene start event
- `'lookAt'` - Camera look-at event
- `'follow'` - Camera follow event
## SPEObject Interface
Objects returned by `findObjectById()` and `findObjectByName()` have the following properties:
### Properties
#### `name`
- **Type**: `string`
- **Description**: Object name from Spline editor
- **Example**: `'Cube'`, `'Button'`, `'Product'`
#### `id`
- **Type**: `string`
- **Description**: Object UUID
- **Example**: `'8E8C2DDD-18B6-4C54-861D-7ED2519DE20E'`
#### `position`
- **Type**: `{ x: number, y: number, z: number }`
- **Description**: Object position in 3D space
- **Mutable**: Yes
- **Example**:
```jsx
console.log(obj.position); // { x: 0, y: 100, z: 50 }
obj.position.x += 10;
obj.position.y = 200;
```
#### `rotation`
- **Type**: `{ x: number, y: number, z: number }`
- **Description**: Object rotation in radians
- **Mutable**: Yes
- **Example**:
```jsx
obj.rotation.y += Math.PI / 4; // Rotate 45 degrees
obj.rotation.x = Math.PI / 2; // Rotate 90 degrees
```
#### `scale`
- **Type**: `{ x: number, y: number, z: number }`
- **Description**: Object scale multiplier
- **Mutable**: Yes
- **Example**:
```jsx
obj.scale.x = 2; // Scale to 2x width
obj.scale.y = 0.5; // Scale to half height
obj.scale.z = 1.5;
```
#### `material`
- **Type**: `Material`
- **Description**: Object material properties
- **Properties**:
- `color` - Material color (use `.set()` method)
- **Example**:
```jsx
// Set color using hex value
obj.material.color.set(0xff6b6b); // Red
obj.material.color.set(0x4ecdc4); // Cyan
obj.material.color.set(0xffe66d); // Yellow
```
#### `visible`
- **Type**: `boolean`
- **Description**: Object visibility
- **Mutable**: Yes
- **Example**:
```jsx
obj.visible = false; // Hide object
obj.visible = true; // Show object
```
### Methods
#### `emitEvent(eventName)`
- **Description**: Triggers an event on this specific object
- **Parameters**:
- `eventName` (SplineEventName) - The event to trigger
- **Returns**: `void`
- **Example**:
```jsx
const button = spline.findObjectByName('Button');
button.emitEvent('mouseHover'); // Trigger hover animation
```
## SplineEvent Interface
Event object passed to event handler callbacks:
### Properties
#### `target`
- **Type**: `SPEObject`
- **Description**: The object that triggered the event
- **Example**:
```jsx
function onSplineMouseDown(e) {
console.log('Clicked object:', e.target.name);
console.log('Object ID:', e.target.id);
console.log('Position:', e.target.position);
}
```
#### `type`
- **Type**: `string`
- **Description**: Event type name
- **Values**: `'mouseDown'`, `'mouseUp'`, `'mouseHover'`, etc.
## Usage Examples
### Complete Component Example
```jsx
import { useRef, useState } from 'react';
import Spline from '@splinetool/react-spline';
export default function InteractiveScene() {
const splineApp = useRef();
const [isLoaded, setIsLoaded] = useState(false);
function onLoad(spline) {
splineApp.current = spline;
setIsLoaded(true);
console.log('Scene loaded');
}
function onSplineMouseDown(e) {
console.log('Clicked:', e.target.name);
if (e.target.name === 'Button') {
// Trigger animation
splineApp.current.emitEvent('mouseHover', 'Card');
}
}
function rotateAllObjects() {
if (!isLoaded) return;
const cube = splineApp.current.findObjectByName('Cube');
const sphere = splineApp.current.findObjectByName('Sphere');
if (cube) cube.rotation.y += Math.PI / 4;
if (sphere) sphere.rotation.x += Math.PI / 4;
}
return (
<div>
<Spline
scene="https://prod.spline.design/YOUR-SCENE-ID/scene.splinecode"
onLoad={onLoad}
onSplineMouseDown={onSplineMouseDown}
style={{ width: '100%', height: '600px' }}
/>
{isLoaded && (
<button onClick={rotateAllObjects}>
Rotate Objects
</button>
)}
</div>
);
}
```
### Next.js Example
```jsx
// app/components/SplineScene.jsx
'use client';
import Spline from '@splinetool/react-spline/next';
export default function SplineScene() {
return (
<div style={{ width: '100vw', height: '100vh' }}>
<Spline scene="https://prod.spline.design/YOUR-SCENE-ID/scene.splinecode" />
</div>
);
}
```
### Lazy Loading Example
```jsx
import React, { Suspense } from 'react';
const Spline = React.lazy(() => import('@splinetool/react-spline'));
export default function LazyScene() {
return (
<Suspense fallback={<div>Loading 3D scene...</div>}>
<Spline scene="https://prod.spline.design/YOUR-SCENE-ID/scene.splinecode" />
</Suspense>
);
}
```
## Color Reference
Common hex color values for `material.color.set()`:
```javascript
// Red tones
0xff0000 // Pure red
0xff6b6b // Coral red
0xe74c3c // Soft red
// Blue tones
0x0000ff // Pure blue
0x3498db // Sky blue
0x2c3e50 // Dark blue
// Green tones
0x00ff00 // Pure green
0x2ecc71 // Emerald green
0x27ae60 // Dark green
// Yellow/Orange
0xffff00 // Pure yellow
0xffe66d // Soft yellow
0xf39c12 // Orange
// Purple/Pink
0x9b59b6 // Purple
0xe91e63 // Pink
0xff69b4 // Hot pink
// Cyan/Teal
0x00ffff // Pure cyan
0x4ecdc4 // Teal
0x1abc9c // Turquoise
// Grayscale
0xffffff // White
0xcccccc // Light gray
0x95a5a6 // Gray
0x7f8c8d // Dark gray
0x000000 // Black
```
## Math Helpers
Common rotation values (radians):
```javascript
Math.PI / 6 // 30 degrees
Math.PI / 4 // 45 degrees
Math.PI / 3 // 60 degrees
Math.PI / 2 // 90 degrees
Math.PI // 180 degrees
Math.PI * 2 // 360 degrees
// Convert degrees to radians
const radians = (degrees * Math.PI) / 180;
// Convert radians to degrees
const degrees = (radians * 180) / Math.PI;
```
## Troubleshooting
### Scene Not Loading
- Verify scene URL is correct and complete
- Check that scene is published in Spline editor
- Look for CORS errors in console
- Ensure `@splinetool/runtime` is installed
### Events Not Firing
- Use `onSplineMouseDown` not `onMouseDown`
- Verify object has events configured in Spline editor
- Check object name matches exactly
### Object Not Found
- Verify object name in Spline editor matches exactly (case-sensitive)
- Check that `onLoad` has been called before finding objects
- Use `findObjectById` if name changes frequently
### Performance Issues
- Enable `renderOnDemand={true}`
- Reduce polygon count in Spline editor
- Compress textures
- Create mobile-specific scenes
- Use lazy loading for heavy scenes
## TypeScript Support
```typescript
import { Application, SPEObject, SplineEvent } from '@splinetool/runtime';
import Spline from '@splinetool/react-spline';
interface Props {
sceneUrl: string;
}
export default function MyScene({ sceneUrl }: Props) {
const splineApp = useRef<Application | null>(null);
function onLoad(app: Application): void {
splineApp.current = app;
}
function onSplineMouseDown(e: SplineEvent): void {
const target: SPEObject = e.target;
console.log(target.name);
}
return (
<Spline
scene={sceneUrl}
onLoad={onLoad}
onSplineMouseDown={onSplineMouseDown}
/>
);
}
```
scripts/component_builder.py
#!/usr/bin/env python3
"""
Spline Component Wrapper Generator
Generates reusable Spline component wrappers with event handling.
Usage:
./component_builder.py # Interactive mode
./component_builder.py --name ProductViewer # CLI mode
"""
import argparse
import sys
from pathlib import Path
COMPONENT_TEMPLATES = {
'basic': '''import Spline from '@splinetool/react-spline';
export default function {name}({{ sceneUrl }}) {{
return (
<div style={{{{ width: '100%', height: '600px' }}}}>
<Spline scene={{sceneUrl}} />
</div>
);
}}
''',
'interactive': '''import {{ useRef, useState }} from 'react';
import Spline from '@splinetool/react-spline';
export default function {name}({{ sceneUrl, onObjectClick }}) {{
const splineApp = useRef();
const [isLoaded, setIsLoaded] = useState(false);
function onLoad(spline) {{
splineApp.current = spline;
setIsLoaded(true);
}}
function onSplineMouseDown(e) {{
console.log('Clicked:', e.target.name);
if (onObjectClick) {{
onObjectClick(e.target);
}}
}}
return (
<Spline
scene={{sceneUrl}}
onLoad={{onLoad}}
onSplineMouseDown={{onSplineMouseDown}}
/>
);
}}
''',
'animated': '''import {{ useRef }} from 'react';
import Spline from '@splinetool/react-spline';
export default function {name}({{ sceneUrl, animationTrigger }}) {{
const splineApp = useRef();
function onLoad(spline) {{
splineApp.current = spline;
}}
// Expose animation controls
const triggerAnimation = (objectName, eventType = 'mouseHover') => {{
if (splineApp.current) {{
splineApp.current.emitEvent(eventType, objectName);
}}
}};
const reverseAnimation = (objectName, eventType = 'mouseHover') => {{
if (splineApp.current) {{
splineApp.current.emitEventReverse(eventType, objectName);
}}
}};
return (
<Spline
scene={{sceneUrl}}
onLoad={{onLoad}}
/>
);
}}
''',
'controlled': '''import {{ useRef, useImperativeHandle, forwardRef }} from 'react';
import Spline from '@splinetool/react-spline';
const {name} = forwardRef(({{ sceneUrl }}, ref) => {{
const splineApp = useRef();
function onLoad(spline) {{
splineApp.current = spline;
}}
// Expose methods to parent component
useImperativeHandle(ref, () => ({{
findObject: (name) => {{
return splineApp.current?.findObjectByName(name);
}},
emitEvent: (eventName, objectName) => {{
splineApp.current?.emitEvent(eventName, objectName);
}},
setZoom: (zoom) => {{
splineApp.current?.setZoom(zoom);
}},
getApp: () => splineApp.current
}}));
return (
<Spline
scene={{sceneUrl}}
onLoad={{onLoad}}
/>
);
}});
export default {name};
''',
'responsive': '''import {{ useRef, useEffect, useState }} from 'react';
import Spline from '@splinetool/react-spline';
export default function {name}({{ mobileSceneUrl, desktopSceneUrl }}) {{
const splineApp = useRef();
const [isMobile, setIsMobile] = useState(false);
useEffect(() => {{
const checkMobile = () => {{
setIsMobile(window.innerWidth < 768);
}};
checkMobile();
window.addEventListener('resize', checkMobile);
return () => window.removeEventListener('resize', checkMobile);
}}, []);
function onLoad(spline) {{
splineApp.current = spline;
// Adjust for mobile
if (isMobile) {{
spline.setZoom(0.7);
}}
}}
const sceneUrl = isMobile ? mobileSceneUrl : desktopSceneUrl;
return (
<div style={{{{
width: '100%',
height: isMobile ? '400px' : '600px'
}}}}>
<Spline
scene={{sceneUrl}}
onLoad={{onLoad}}
/>
</div>
);
}}
''',
'lazy': '''import React, {{ Suspense }} from 'react';
const Spline = React.lazy(() => import('@splinetool/react-spline'));
export default function {name}({{ sceneUrl, fallback }}) {{
return (
<Suspense fallback={{fallback || <div>Loading 3D scene...</div>}}>
<div style={{{{ width: '100%', height: '600px' }}}}>
<Spline scene={{sceneUrl}} />
</div>
</Suspense>
);
}}
'''
}
def generate_component(name, component_type, output_path=None):
"""Generate component code"""
if component_type not in COMPONENT_TEMPLATES:
print(f"❌ Unknown component type: {component_type}")
print(f"Available types: {', '.join(COMPONENT_TEMPLATES.keys())}")
sys.exit(1)
template = COMPONENT_TEMPLATES[component_type]
code = template.format(name=name)
# Output to file or stdout
if output_path:
output_file = Path(output_path)
output_file.parent.mkdir(parents=True, exist_ok=True)
with open(output_file, 'w') as f:
f.write(code)
print(f"✅ Component created: {output_file}")
else:
print("\n" + "=" * 60)
print(f"// {name}.jsx")
print("=" * 60)
print(code)
print("=" * 60)
def main():
parser = argparse.ArgumentParser(
description='Generate Spline component wrappers'
)
parser.add_argument(
'--name',
type=str,
help='Component name (e.g., ProductViewer)'
)
parser.add_argument(
'--type',
type=str,
choices=list(COMPONENT_TEMPLATES.keys()),
help='Component type'
)
parser.add_argument(
'--output',
type=str,
help='Output file path (default: print to stdout)'
)
args = parser.parse_args()
# Interactive mode
if not args.name or not args.type:
print("🎨 Spline Component Builder\n")
if not args.name:
name = input("Component name (e.g., ProductViewer): ").strip()
if not name:
print("❌ Component name is required")
sys.exit(1)
else:
name = args.name
if not args.type:
print("\nComponent types:")
for i, comp_type in enumerate(COMPONENT_TEMPLATES.keys(), 1):
print(f" {i}. {comp_type}")
choice = input("\nSelect type (1-6): ").strip()
try:
comp_type = list(COMPONENT_TEMPLATES.keys())[int(choice) - 1]
except (ValueError, IndexError):
print("❌ Invalid choice")
sys.exit(1)
else:
comp_type = args.type
output_path = args.output
else:
name = args.name
comp_type = args.type
output_path = args.output
# Generate component
generate_component(name, comp_type, output_path)
if __name__ == '__main__':
main()
scripts/project_generator.py
#!/usr/bin/env python3
"""
Spline + React Project Generator
Generates boilerplate React projects with Spline integration.
Usage:
./project_generator.py # Interactive mode
./project_generator.py --name my-project # CLI mode
"""
import argparse
import json
import os
import sys
from pathlib import Path
def create_project_structure(project_name, include_nextjs=False):
"""Create complete project structure with all necessary files"""
base_path = Path(project_name)
# Create directories
directories = [
base_path,
base_path / 'src',
base_path / 'public',
]
for directory in directories:
directory.mkdir(parents=True, exist_ok=True)
# package.json
package_json = {
"name": project_name,
"version": "0.1.0",
"private": True,
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0",
"@splinetool/react-spline": "^2.2.6",
"@splinetool/runtime": "^0.9.508"
},
"devDependencies": {
"@vitejs/plugin-react": "^4.0.0",
"vite": "^4.3.9"
},
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
}
}
if include_nextjs:
package_json["dependencies"]["next"] = "^14.0.0"
package_json["scripts"] = {
"dev": "next dev",
"build": "next build",
"start": "next start"
}
with open(base_path / 'package.json', 'w') as f:
json.dump(package_json, f, indent=2)
# vite.config.js (if not Next.js)
if not include_nextjs:
vite_config = """import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
server: {
port: 3000
}
});
"""
with open(base_path / 'vite.config.js', 'w') as f:
f.write(vite_config)
# index.html (if not Next.js)
if not include_nextjs:
index_html = """<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Spline React App</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>
"""
with open(base_path / 'index.html', 'w') as f:
f.write(index_html)
# src/main.jsx (if not Next.js)
if not include_nextjs:
main_jsx = """import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import './App.css';
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<App />
</React.StrictMode>
);
"""
with open(base_path / 'src' / 'main.jsx', 'w') as f:
f.write(main_jsx)
# src/App.jsx or app/page.jsx
if include_nextjs:
app_dir = base_path / 'app'
app_dir.mkdir(exist_ok=True)
page_jsx = """'use client';
import Spline from '@splinetool/react-spline/next';
import { useRef } from 'react';
export default function Home() {
const splineApp = useRef();
function onLoad(spline) {
splineApp.current = spline;
console.log('Spline scene loaded');
}
function onSplineMouseDown(e) {
console.log('Clicked:', e.target.name);
}
return (
<main style={{ width: '100vw', height: '100vh' }}>
<Spline
scene="https://prod.spline.design/YOUR-SCENE-ID/scene.splinecode"
onLoad={onLoad}
onSplineMouseDown={onSplineMouseDown}
/>
</main>
);
}
"""
with open(app_dir / 'page.jsx', 'w') as f:
f.write(page_jsx)
else:
app_jsx = """import { useRef, useState } from 'react';
import Spline from '@splinetool/react-spline';
export default function App() {
const splineApp = useRef();
const [isLoaded, setIsLoaded] = useState(false);
function onLoad(spline) {
splineApp.current = spline;
setIsLoaded(true);
console.log('Spline scene loaded');
}
function onSplineMouseDown(e) {
console.log('Clicked:', e.target.name);
if (e.target.name === 'Button') {
splineApp.current.emitEvent('mouseHover', 'Card');
}
}
return (
<div className="app">
<div className="scene-container">
<Spline
scene="https://prod.spline.design/YOUR-SCENE-ID/scene.splinecode"
onLoad={onLoad}
onSplineMouseDown={onSplineMouseDown}
/>
</div>
{isLoaded && (
<div className="controls">
<button onClick={() => splineApp.current.setZoom(1.5)}>
Zoom In
</button>
<button onClick={() => splineApp.current.setZoom(0.7)}>
Zoom Out
</button>
</div>
)}
</div>
);
}
"""
with open(base_path / 'src' / 'App.jsx', 'w') as f:
f.write(app_jsx)
# CSS
if not include_nextjs:
app_css = """* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.app {
width: 100vw;
height: 100vh;
position: relative;
}
.scene-container {
width: 100%;
height: 100%;
}
.controls {
position: absolute;
top: 20px;
left: 20px;
display: flex;
gap: 10px;
z-index: 10;
}
button {
padding: 10px 20px;
background: rgba(255, 255, 255, 0.9);
border: none;
border-radius: 8px;
cursor: pointer;
font-size: 14px;
font-weight: 500;
transition: all 0.2s;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
button:hover {
background: white;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
button:active {
transform: scale(0.98);
}
"""
with open(base_path / 'src' / 'App.css', 'w') as f:
f.write(app_css)
# README.md
readme = f"""# {project_name}
Spline + React project
## Setup
```bash
npm install
```
## Development
```bash
npm run dev
```
Visit http://localhost:3000
## Update Scene URL
Replace `YOUR-SCENE-ID` in the code with your actual Spline scene ID.
Get your scene URL from Spline:
1. Open your scene in Spline editor
2. Click "Export" button
3. Select "Code Export" > "React"
4. Copy the scene URL
## Build
```bash
npm run build
```
## Learn More
- [Spline Documentation](https://docs.spline.design)
- [React Spline GitHub](https://github.com/splinetool/react-spline)
"""
with open(base_path / 'README.md', 'w') as f:
f.write(readme)
print(f"\n✅ Project '{project_name}' created successfully!")
print(f"\nNext steps:")
print(f" cd {project_name}")
print(f" npm install")
print(f" npm run dev")
print(f"\n📝 Don't forget to update the Spline scene URL!")
def main():
parser = argparse.ArgumentParser(
description='Generate Spline + React project boilerplate'
)
parser.add_argument(
'--name',
type=str,
help='Project name'
)
parser.add_argument(
'--nextjs',
action='store_true',
help='Use Next.js instead of Vite'
)
args = parser.parse_args()
# Interactive mode
if not args.name:
print("🚀 Spline + React Project Generator\n")
project_name = input("Project name: ").strip()
if not project_name:
print("❌ Project name is required")
sys.exit(1)
use_nextjs = input("Use Next.js? (y/N): ").strip().lower() == 'y'
else:
project_name = args.name
use_nextjs = args.nextjs
# Create project
create_project_structure(project_name, use_nextjs)
if __name__ == '__main__':
main()
SKILL.md
---
name: spline-interactive
description: Browser-based 3D design tool with visual editor, animation, and web export. Use this skill when creating 3D scenes without code, designing interactive web experiences, prototyping 3D UI, exporting to React/web, or building designer-friendly 3D content. Triggers on tasks involving Spline, no-code 3D, visual 3D editor, 3D animation, state-based interactions, React Spline integration, or scene export. Alternative to Three.js for designers who prefer visual tools over code.
---
# Spline Interactive - Browser-Based 3D Design and Animation
## Overview
Spline is a browser-based 3D design and animation platform that enables creators to build interactive 3D experiences without requiring code or specialized software knowledge. It provides a collaborative visual editor for designing, animating, and exporting 3D scenes across multiple platforms.
**Key Features**:
- Visual 3D modeling with parametric shapes, extrusion, and boolean operations
- State-based animation system with timeline controls
- Interactive event system (mouse, keyboard, collision, scroll)
- Multiple export options (React components, web code, public URLs)
- Real-time collaboration with team libraries
- AI-powered generation (3D from text, textures, style transfer)
- Built-in physics and particle systems
**When to Use This Skill**:
- Creating 3D web experiences without writing Three.js code
- Designing interactive product showcases or configurators
- Prototyping 3D UI/UX concepts visually
- Building marketing pages with 3D elements
- Collaborating with designers on 3D content
- Exporting scenes for React or vanilla JS integration
**Alternatives**:
- **Three.js** (threejs-webgl): For developers who prefer code-first approach and need maximum control
- **Babylon.js** (babylonjs-engine): For game-focused projects with built-in physics
- **React Three Fiber** (react-three-fiber): For React developers who want to build 3D with JSX
## Core Concepts
### 1. Scene Structure
Spline organizes projects into scenes containing:
- **Objects**: 3D models, shapes, text, images
- **Lights**: Directional, point, spot lights
- **Cameras**: Orbital, perspective, orthographic
- **Events**: Interaction triggers
- **States**: Animation keyframes
### 2. Components System
Reusable elements that can be:
- Created from any object or group
- Instantiated multiple times
- Updated across all instances
- Overridden per instance
### 3. State-Based Animation
Animations are defined as transitions between states:
- **Default State**: Initial appearance
- **Additional States**: Target appearances
- **Events**: Triggers that cause state transitions
- **Transitions**: Duration, easing, properties
### 4. Interactivity Model
Event-driven system with:
- **Events**: User actions or scene triggers
- **Conditions**: Logic gates (if/else)
- **Actions**: State changes, audio, scene switches
- **Variables**: Dynamic data from APIs or user input
### 5. Export Options
Multiple deployment methods:
- **Public URL**: Direct shareable link
- **Code Export**: React component or vanilla JS
- **Spline Viewer**: Embedded iframe
- **Self-Hosted**: Download and host independently
## Common Patterns
### Pattern 1: Basic React Integration
**Use Case**: Embed a Spline scene in a React application
**Implementation**:
```bash
# Installation
npm install @splinetool/react-spline @splinetool/runtime
```
```jsx
import Spline from '@splinetool/react-spline';
export default function Hero() {
return (
<div style={{ width: '100%', height: '600px' }}>
<Spline scene="https://prod.spline.design/YOUR-SCENE-ID/scene.splinecode" />
</div>
);
}
```
**Key Points**:
- Scene URL comes from Spline export dialog
- Component fills parent container
- Automatically handles loading and rendering
### Pattern 2: Event Handling and Object Interaction
**Use Case**: Respond to user clicks on specific objects
**Implementation**:
```jsx
import Spline from '@splinetool/react-spline';
export default function InteractiveScene() {
function onSplineMouseDown(e) {
// Check if clicked object is the button
if (e.target.name === 'Button') {
console.log('Button clicked!');
// Get object properties
console.log('Position:', e.target.position);
console.log('Rotation:', e.target.rotation);
console.log('Scale:', e.target.scale);
}
}
function onSplineMouseHover(e) {
if (e.target.name === 'Button') {
console.log('Hovering over button');
}
}
return (
<Spline
scene="https://prod.spline.design/YOUR-SCENE-ID/scene.splinecode"
onSplineMouseDown={onSplineMouseDown}
onSplineMouseHover={onSplineMouseHover}
/>
);
}
```
**Available Event Handlers**:
- `onSplineMouseDown` - Mouse press on object
- `onSplineMouseUp` - Mouse release
- `onSplineMouseHover` - Mouse over object
- `onSplineKeyDown` - Keyboard press
- `onSplineKeyUp` - Keyboard release
- `onSplineStart` - Scene loaded and started
- `onSplineLookAt` - Camera look-at event
- `onSplineFollow` - Camera follow event
- `onSplineScroll` - Scroll event
### Pattern 3: Programmatic Object Control
**Use Case**: Modify object properties from React code
**Implementation**:
```jsx
import { useRef } from 'react';
import Spline from '@splinetool/react-spline';
export default function ProductViewer() {
const cube = useRef();
const splineApp = useRef();
function onLoad(spline) {
// Save Spline instance
splineApp.current = spline;
// Find object by name
const obj = spline.findObjectByName('Product');
// Or by ID
// const obj = spline.findObjectById('8E8C2DDD-18B6-4C54-861D-7ED2519DE20E');
cube.current = obj;
}
function rotateProduct() {
if (cube.current) {
// Rotate 45 degrees around Y axis
cube.current.rotation.y += Math.PI / 4;
}
}
function changeColor() {
if (cube.current) {
// Change material color (hex color)
cube.current.material.color.set(0xff6b6b);
}
}
function moveProduct() {
if (cube.current) {
cube.current.position.x += 50;
cube.current.position.y += 10;
}
}
return (
<div>
<Spline
scene="https://prod.spline.design/YOUR-SCENE-ID/scene.splinecode"
onLoad={onLoad}
/>
<div style={{ position: 'absolute', top: 20, left: 20 }}>
<button onClick={rotateProduct}>Rotate</button>
<button onClick={changeColor}>Change Color</button>
<button onClick={moveProduct}>Move</button>
</div>
</div>
);
}
```
**Object Properties You Can Modify**:
- `position` - { x, y, z }
- `rotation` - { x, y, z } (radians)
- `scale` - { x, y, z }
- `material.color` - Color hex value
- `visible` - Boolean
### Pattern 4: Triggering Spline Animations
**Use Case**: Trigger animations defined in Spline from React
**Implementation**:
```jsx
import { useRef } from 'react';
import Spline from '@splinetool/react-spline';
export default function AnimatedCard() {
const splineApp = useRef();
function onLoad(app) {
splineApp.current = app;
}
function triggerHoverAnimation() {
// Emit mouseHover event on 'Card' object
splineApp.current.emitEvent('mouseHover', 'Card');
}
function triggerClickAnimation() {
// Emit mouseDown event on 'Button' object
splineApp.current.emitEvent('mouseDown', 'Button');
}
function reverseAnimation() {
// Play animation in reverse
splineApp.current.emitEventReverse('mouseHover', 'Card');
}
return (
<div>
<Spline
scene="https://prod.spline.design/YOUR-SCENE-ID/scene.splinecode"
onLoad={onLoad}
/>
<button onClick={triggerHoverAnimation}>Hover Effect</button>
<button onClick={triggerClickAnimation}>Click Effect</button>
<button onClick={reverseAnimation}>Reverse</button>
</div>
);
}
```
**Available Event Types**:
- `mouseDown` - Mouse press
- `mouseHover` - Hover effect
- `mouseUp` - Mouse release
- `keyDown` - Key press
- `keyUp` - Key release
- `start` - Start event
- `lookAt` - Look at camera
- `follow` - Follow camera
### Pattern 5: Next.js Integration with SSR
**Use Case**: Use Spline in Next.js with server-side rendering benefits
**Implementation**:
```jsx
// app/page.js (Next.js 13+ App Router)
import Spline from '@splinetool/react-spline/next';
export default function Home() {
return (
<main>
<div style={{ width: '100vw', height: '100vh' }}>
<Spline scene="https://prod.spline.design/YOUR-SCENE-ID/scene.splinecode" />
</div>
</main>
);
}
```
**Benefits**:
- Placeholder image shown during SSR
- Faster perceived load times
- Better SEO with fallback content
### Pattern 6: Lazy Loading for Performance
**Use Case**: Defer Spline loading until needed
**Implementation**:
```jsx
import React, { Suspense } from 'react';
// Dynamically import Spline
const Spline = React.lazy(() => import('@splinetool/react-spline'));
export default function LazyScene() {
return (
<div>
<h1>My Page Content</h1>
<Suspense fallback={<div>Loading 3D scene...</div>}>
<div style={{ width: '100%', height: '500px' }}>
<Spline scene="https://prod.spline.design/YOUR-SCENE-ID/scene.splinecode" />
</div>
</Suspense>
<p>More content below</p>
</div>
);
}
```
**Benefits**:
- Reduces initial bundle size
- Improves page load performance
- Shows custom loading UI
### Pattern 7: Responsive Spline Scenes
**Use Case**: Make Spline scenes adapt to different screen sizes
**Implementation**:
```jsx
import Spline from '@splinetool/react-spline';
import { useState, useEffect } from 'react';
export default function ResponsiveScene() {
const [isMobile, setIsMobile] = useState(false);
useEffect(() => {
const checkMobile = () => {
setIsMobile(window.innerWidth < 768);
};
checkMobile();
window.addEventListener('resize', checkMobile);
return () => window.removeEventListener('resize', checkMobile);
}, []);
return (
<div style={{
width: '100%',
height: isMobile ? '400px' : '600px'
}}>
<Spline
scene={
isMobile
? "https://prod.spline.design/YOUR-MOBILE-SCENE/scene.splinecode"
: "https://prod.spline.design/YOUR-DESKTOP-SCENE/scene.splinecode"
}
/>
</div>
);
}
```
**Alternative Approach** (Single Scene):
```jsx
import Spline from '@splinetool/react-spline';
import { useRef, useEffect } from 'react';
export default function ResponsiveScene() {
const splineApp = useRef();
function onLoad(app) {
splineApp.current = app;
adjustForScreenSize();
}
function adjustForScreenSize() {
if (!splineApp.current) return;
const camera = splineApp.current.findObjectByName('Camera');
const isMobile = window.innerWidth < 768;
if (isMobile) {
// Zoom out on mobile
splineApp.current.setZoom(0.7);
// Adjust camera position
camera.position.z = 1500;
}
}
useEffect(() => {
window.addEventListener('resize', adjustForScreenSize);
return () => window.removeEventListener('resize', adjustForScreenSize);
}, []);
return (
<Spline
scene="https://prod.spline.design/YOUR-SCENE-ID/scene.splinecode"
onLoad={onLoad}
/>
);
}
```
## Integration Patterns
### With Three.js (threejs-webgl)
For advanced use cases, combine Spline-designed assets with Three.js code:
1. **Export from Spline**: Use GLTF/GLB export
2. **Import in Three.js**: Load using GLTFLoader
3. **Enhance with code**: Add custom shaders, physics, or effects
```javascript
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
const loader = new GLTFLoader();
loader.load('spline-model.glb', (gltf) => {
scene.add(gltf.scene);
// Add custom behaviors
});
```
### With GSAP (gsap-scrolltrigger)
Trigger Spline animations on scroll:
```jsx
import { useEffect, useRef } from 'react';
import Spline from '@splinetool/react-spline';
import gsap from 'gsap';
import ScrollTrigger from 'gsap/ScrollTrigger';
gsap.registerPlugin(ScrollTrigger);
export default function ScrollAnimated() {
const splineApp = useRef();
function onLoad(app) {
splineApp.current = app;
ScrollTrigger.create({
trigger: '.scene-container',
start: 'top center',
onEnter: () => {
app.emitEvent('mouseHover', 'Product');
}
});
}
return (
<div className="scene-container">
<Spline
scene="https://prod.spline.design/YOUR-SCENE-ID/scene.splinecode"
onLoad={onLoad}
/>
</div>
);
}
```
### With Framer Motion (motion-framer)
Animate container while Spline handles 3D:
```jsx
import { motion } from 'framer-motion';
import Spline from '@splinetool/react-spline';
export default function AnimatedContainer() {
return (
<motion.div
initial={{ opacity: 0, y: 50 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8 }}
style={{ width: '100%', height: '600px' }}
>
<Spline scene="https://prod.spline.design/YOUR-SCENE-ID/scene.splinecode" />
</motion.div>
);
}
```
## Performance Optimization
### 1. Enable On-Demand Rendering
Render only when scene changes, not every frame:
```jsx
<Spline
scene="https://prod.spline.design/YOUR-SCENE-ID/scene.splinecode"
renderOnDemand={true} // Default is true
/>
```
### 2. Optimize Scene in Spline Editor
**In Spline**:
- Reduce polygon count (use decimation)
- Compress textures (lower resolution, use JPG over PNG)
- Limit lights (2-3 lights maximum)
- Use simple materials when possible
- Enable LOD (Level of Detail) for distant objects
### 3. Lazy Load Heavy Scenes
Use React.lazy() as shown in Pattern 6
### 4. Preload Critical Scenes
```jsx
import { useEffect } from 'react';
import Spline from '@splinetool/react-spline';
export default function PreloadedScene() {
useEffect(() => {
// Preload scene assets
const link = document.createElement('link');
link.rel = 'prefetch';
link.href = 'https://prod.spline.design/YOUR-SCENE-ID/scene.splinecode';
document.head.appendChild(link);
}, []);
return (
<Spline scene="https://prod.spline.design/YOUR-SCENE-ID/scene.splinecode" />
);
}
```
### 5. Mobile Optimizations
- Create separate mobile scenes with lower detail
- Reduce canvas resolution on mobile
- Disable shadows and reflections
- Use simpler materials
```jsx
<Spline
scene={isMobile ? mobileSceneUrl : desktopSceneUrl}
style={{
width: '100%',
height: isMobile ? '300px' : '600px'
}}
/>
```
## Common Pitfalls and Solutions
### Pitfall 1: Scene Not Loading
**Problem**: Spline component renders but scene doesn't appear
**Solutions**:
```jsx
// ❌ Wrong: Invalid scene URL
<Spline scene="my-scene.splinecode" />
// ✅ Correct: Full URL from Spline export
<Spline scene="https://prod.spline.design/KFonZGtsoUXP-qx7/scene.splinecode" />
// Check for errors
function onLoad(app) {
console.log('Scene loaded successfully', app);
}
<Spline scene={sceneUrl} onLoad={onLoad} />
```
**Also Check**:
- Scene is published in Spline editor
- Network tab shows successful file downloads
- No CORS errors in console
### Pitfall 2: Object References Lost After Re-render
**Problem**: Object refs become undefined after component updates
**Solution**:
```jsx
// ❌ Wrong: Storing objects without proper refs
let myObject;
function onLoad(spline) {
myObject = spline.findObjectByName('Cube'); // Lost on re-render
}
// ✅ Correct: Use React refs
const myObject = useRef();
function onLoad(spline) {
myObject.current = spline.findObjectByName('Cube');
}
```
### Pitfall 3: Performance Issues on Mobile
**Problem**: Scene runs slowly on mobile devices
**Solutions**:
```jsx
// Create mobile-optimized version in Spline editor
// - Fewer polygons (< 50k triangles)
// - Smaller textures (512x512 or less)
// - No shadows or reflections
// - Simpler materials
// Load appropriate version
const isMobile = window.innerWidth < 768;
const sceneUrl = isMobile
? 'https://prod.spline.design/MOBILE-SCENE/scene.splinecode'
: 'https://prod.spline.design/DESKTOP-SCENE/scene.splinecode';
<Spline scene={sceneUrl} renderOnDemand={true} />
```
### Pitfall 4: Events Not Firing
**Problem**: Click or hover events don't trigger
**Solutions**:
```jsx
// ❌ Wrong: Using wrong event name
<Spline onMouseDown={handler} /> // Not a Spline prop
// ✅ Correct: Use Spline event props
<Spline onSplineMouseDown={handler} />
// Also ensure object has events in Spline editor:
// 1. Select object in Spline
// 2. Add event in "Events" panel
// 3. Assign state transition or action
```
### Pitfall 5: Animation Not Triggering Programmatically
**Problem**: `emitEvent()` doesn't trigger animation
**Solutions**:
```jsx
// ❌ Wrong: Calling before scene loads
function triggerAnimation() {
splineApp.current.emitEvent('mouseHover', 'Button'); // Error if not loaded
}
// ✅ Correct: Ensure scene is loaded
const [isLoaded, setIsLoaded] = useState(false);
function onLoad(app) {
splineApp.current = app;
setIsLoaded(true);
}
function triggerAnimation() {
if (isLoaded && splineApp.current) {
splineApp.current.emitEvent('mouseHover', 'Button');
}
}
// Also verify in Spline editor:
// - Object has the correct name ('Button')
// - mouseHover event is configured
// - Event has action (state transition, etc.)
```
### Pitfall 6: Hydration Errors in Next.js
**Problem**: Mismatch between server and client render
**Solution**:
```jsx
// ❌ Wrong: Using standard import in Next.js
import Spline from '@splinetool/react-spline';
// ✅ Correct: Use Next.js-specific import
import Spline from '@splinetool/react-spline/next';
// Or use dynamic import with ssr: false
import dynamic from 'next/dynamic';
const Spline = dynamic(
() => import('@splinetool/react-spline'),
{ ssr: false }
);
```
## Resources
### Official Documentation
- **Spline Docs**: https://docs.spline.design
- **React Spline GitHub**: https://github.com/splinetool/react-spline
- **Spline Community**: https://spline.community
### Spline Editor
- **Web App**: https://app.spline.design
- **Desktop App**: Available for macOS, Windows, Linux
### Learning Resources
- **Tutorials**: https://spline.design/tutorials
- **YouTube Channel**: Official Spline tutorials
- **Examples Gallery**: https://spline.design/community
### Export Formats
- React component (via `@splinetool/react-spline`)
- Vanilla JavaScript (Web Code API)
- GLTF/GLB (for Three.js, Babylon.js)
- USDZ (for Apple AR)
- STL (for 3D printing)
- Video/GIF (for marketing)
## Related Skills
- **threejs-webgl**: For code-first 3D development with more control
- **react-three-fiber**: For building 3D scenes with React and JSX
- **babylonjs-engine**: Alternative 3D engine with editor workflow
- **motion-framer**: For animating Spline containers and UI elements
- **gsap-scrolltrigger**: For scroll-driven Spline animations
- **figma-dev-mode**: For design-to-code workflow (similar visual approach)
## Scripts
This skill includes utility scripts:
- `project_generator.py` - Generate Spline + React starter projects
- `component_builder.py` - Build Spline component wrappers with events
Run scripts from the skill directory:
```bash
./scripts/project_generator.py
./scripts/component_builder.py
```
## Assets
Starter templates and examples:
- `starter_spline/` - Complete React + Spline template
- `examples/` - Real-world integration patterns