Skip to content

raymondreaming/spline

Repository files navigation

spline

React hooks for controlling Spline 3D scenes. Abstractions over @splinetool/react-spline for common patterns.

Setup

bun install
bun dev

Actions

Import from @/actions:

import {
  useObjectMover,
  useAnimation,
  useVisibility,
  combineLoaders
} from "@/actions";

API Features

String shorthand: All hooks accept either an object name string or full options:

// String shorthand
const mover = useObjectMover("Cube");

// Full options
const mover = useObjectMover({
  objectName: "Cube",
  initialOffset: { z: -100 },
  onError: (err) => console.error(err)
});

Combine multiple hooks: Use combineLoaders when using multiple hooks on the same Spline:

const mover = useObjectMover("Cube");
const rotator = useObjectRotator("Sphere");
const camera = useCameraMover();

<Spline
  scene={url}
  onLoad={combineLoaders(mover.onLoad, rotator.onLoad, camera.onLoad)}
/>

Standalone functions: For one-off operations when you have the spline instance:

import { moveObject, rotateObject, scaleObject, setVisibility } from "@/actions";

// Inside onLoad or with existing spline ref
moveObject(spline, { objectName: "Cube", position: { x: 100 } });
rotateObject(spline, { objectName: "Globe", rotation: { y: Math.PI } });
scaleObject(spline, { objectName: "Button", scale: { x: 2, y: 2, z: 2 } });
setVisibility(spline, "Tooltip", false);

useSpline

Base hook for spline instance access.

const { onLoad, findObject, setVariable, emitEvent } = useSpline();

Use cases:

  • Direct access to the Spline application instance
  • Finding objects by name or ID for custom manipulations
  • Setting variables that aren't covered by other hooks
  • Emitting custom events for complex interactions

Why isolate: Foundation for all other hooks. Provides escape hatch when you need raw Spline API access without reimplementing boilerplate.


useSplineReady

Track when Spline finishes loading.

const { isReady, onLoad } = useSplineReady();

return (
  <>
    {!isReady && <LoadingSpinner />}
    <Spline scene={url} onLoad={onLoad} />
  </>
);

Use cases:

  • Show loading states while Spline initializes
  • Trigger effects after scene is ready
  • Conditional rendering based on load state

Why isolate: Loading state is a common need. Avoids manual useState/useRef setup.


useObjectTransform

Control all transforms (position, rotation, scale) of an object.

const { onLoad, setPosition, setRotation, setScale, getTransform } = useObjectTransform("Cube");

Use cases:

  • Complex animations requiring multiple transform types
  • Full object manipulation in one hook
  • Getting current transform state

Why isolate: Combines mover, rotator, and scaler when you need all three. Avoids using combineLoaders for simple cases.


useObjectMover

Move objects by name.

// String shorthand
const mover = useObjectMover("Cube");

// Full options
const { onLoad, move, setPosition } = useObjectMover({
  objectName: "Cube",
  initialOffset: { z: -100 }
});

Use cases:

  • Slide-in animations triggered by user actions
  • Repositioning UI elements based on state
  • Drag-and-drop style interactions
  • Parallax effects on scroll

Why isolate: Position changes are the most common 3D manipulation. Eliminates repetitive ref setup and null checks across components.


useObjectRotator

Rotate objects by name.

// String shorthand
const rotator = useObjectRotator("Globe");

// Full options
const { onLoad, rotate, setRotation } = useObjectRotator({
  objectName: "Globe",
  initialRotation: { y: Math.PI / 4 }
});

Use cases:

  • Spinning product showcases
  • Loading spinners in 3D
  • Interactive globes or dials
  • Rotate-on-hover effects for cards

Why isolate: Rotation logic is separate from position logic. Keeps components focused on one transform type, easier to combine or swap.


useObjectScaler

Scale objects by name.

// String shorthand
const scaler = useObjectScaler("Button");

// Full options
const { onLoad, scale, setScale, scaleUniform } = useObjectScaler({
  objectName: "Button",
  initialScale: { x: 1.5, y: 1.5, z: 1.5 }
});

Use cases:

  • Hover states that grow/shrink elements
  • Pulse animations for attention
  • Data visualization (scale bars by value)
  • Zoom effects on selection

Why isolate: Scale is often animated independently. scaleUniform is a common need that would otherwise require setting x/y/z separately.


useCameraMover

Control camera position.

const { onLoad, pan, setPosition } = useCameraMover({
  cameraName: "Camera",
  initialOffset: { y: 500 }
});

Use cases:

  • Cinematic intro sequences
  • Focus on different scene areas based on navigation
  • Zoom in/out on user interaction
  • Guided tours through 3D spaces

Why isolate: Camera is a special object that controls the entire viewport. Dedicated hook prevents accidentally moving regular objects when you meant to move the camera.


useTextChanger

Change text via Spline variables.

const { onLoad, setText } = useTextChanger({
  variableName: "HeaderText",
  initialText: "Welcome"
});

Use cases:

  • Dynamic labels that update from API data
  • User-customized text in 3D scenes
  • Real-time counters or scores
  • Localization/i18n in 3D

Why isolate: Text in Spline uses variables, not direct object manipulation. This abstraction hides that implementation detail.


useVariables

Manage multiple variables.

const { onLoad, set, setMany } = useVariables({
  initialValues: { Title: "Hello", Count: 0 }
});

Use cases:

  • Dashboard scenes with multiple data points
  • Forms reflected in 3D
  • Game state (score, health, level)
  • Theming (swap multiple colors at once)

Why isolate: When you have many variables, setting them one-by-one is tedious. setMany batches updates cleanly.


useAnimation

Trigger Spline animations via emitEvent.

// String shorthand
const anim = useAnimation("Door");

// Full options
const { onLoad, play, reverse, toggle, isPlaying } = useAnimation({
  objectName: "Door",
  eventType: "mouseDown"
});

Use cases:

  • Open/close doors, drawers, menus
  • Play intro animations on page load
  • Trigger celebrations on success
  • Toggle between two animation states

Why isolate: Spline's emitEvent API is not intuitive. This provides semantic play/reverse/toggle that match mental models.


useAnimationSequence

Play a sequence of animations with delays.

const { onLoad, playSequence, stop } = useAnimationSequence({
  steps: [
    { objectName: "Door", delay: 0 },
    { objectName: "Light", delay: 500 },
    { objectName: "Sound", delay: 1000 }
  ],
  loop: false
});

Use cases:

  • Orchestrated intro sequences
  • Step-by-step tutorials
  • Domino-effect animations
  • Looping ambient animations

Why isolate: Coordinating multiple timed animations requires managing timeouts. This handles cleanup and provides stop/loop controls.


useVisibility

Toggle object visibility.

// String shorthand
const tooltip = useVisibility("Tooltip");

// Full options
const { onLoad, show, hide, toggle, isVisible } = useVisibility({
  objectName: "Tooltip",
  initialVisible: false
});

Use cases:

  • Tooltips that appear on hover
  • Show/hide UI panels
  • Reveal elements on scroll
  • Conditional 3D content

Why isolate: Visibility is binary but needs state sync with React. This keeps React state and Spline state in sync automatically.


useMultiVisibility

Control visibility of multiple objects.

const { onLoad, showAll, hideAll, showOnly } = useMultiVisibility({
  objectNames: ["Step1", "Step2", "Step3"],
  initialVisible: false
});

showOnly("Step2");

Use cases:

  • Multi-step wizards or onboarding
  • Tab-like interfaces in 3D
  • Before/after comparisons
  • Feature toggles in product demos

Why isolate: showOnly pattern (show one, hide others) is extremely common for stepped UIs. Would require manual loops otherwise.


useHoverEffect

Track hover state on objects.

const { isHovered, hoveredObject, onSplineMouseHover } = useHoverEffect({
  objectNames: ["Button", "Card"],
  onEnter: (name) => console.log(`Hovering ${name}`),
  onLeave: (name) => console.log(`Left ${name}`)
});

Use cases:

  • Cursor changes on hoverable objects
  • Info panels that follow hover
  • Highlight effects managed in React
  • Analytics tracking for 3D interactions

Why isolate: Spline's hover event fires continuously. This normalizes it to enter/leave events and tracks current hover state.


useHoverAnimation

Trigger hover animations automatically.

const { spline, onLoad } = useSpline();
const { onSplineMouseHover } = useHoverAnimation({
  spline,
  objectNames: ["Button", "Card"]
});

Use cases:

  • Glow effects on hover
  • Scale-up animations on buttons
  • Color shifts on interactive elements
  • Any Spline-defined hover animation

Why isolate: Connects React hover events to Spline's animation system. Handles enter/leave and forward/reverse automatically.


useModalTrigger

Open modals on object click.

const { isOpen, objectName, onSplineMouseDown, close } = useModalTrigger({
  triggerObjects: ["ClickMe"]
});

Use cases:

  • Product detail modals in 3D catalogs
  • Info popups on hotspots
  • Confirmation dialogs from 3D buttons
  • Lightboxes triggered by 3D thumbnails

Why isolate: Modal state management with Spline clicks is a common pattern. Provides isOpen, objectName, and close handler in one hook.


useObjectClick

Generic click handler.

const { onSplineMouseDown } = useObjectClick({
  objectNames: ["Button"],
  onClick: (name) => console.log(name)
});

Use cases:

  • Navigation triggered by 3D objects
  • Selection mechanics in 3D interfaces
  • Custom click handlers per object
  • Routing based on clicked object

Why isolate: When you don't need modal state, just a clean click callback. Filters to specific objects without switch statements.


Utilities

combineLoaders

Combine multiple onLoad callbacks when using multiple hooks:

import { combineLoaders } from "@/actions";

const mover = useObjectMover("Cube");
const rotator = useObjectRotator("Sphere");

<Spline
  scene={url}
  onLoad={combineLoaders(mover.onLoad, rotator.onLoad)}
/>

Error Handling

All hooks support an onError callback for custom error handling:

const mover = useObjectMover({
  objectName: "MissingObject",
  onError: (error) => {
    // error.code: "OBJECT_NOT_FOUND" | "SPLINE_NOT_LOADED" | "INVALID_OPERATION"
    // error.message: Human readable message
    // error.objectName: The object that caused the error
    console.error(`${error.code}: ${error.message}`);
  }
});

Examples

  • /onclick-move-object - Move object on button click
  • /onload-move-camera - Move camera on load
  • /onload-move-game-object - Move grouped objects
  • /onload-text-change - Change text on load
  • /onmousedown-open-modal - Open modal on click

About

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors