Three.js effect

threeEffect connects Motion’s animation engine to Three.js objects, materials, and uniforms. Register it once, then use the same animate() API you use for DOM elements and plain objects.

import { animate } from '@humanspeak/svelte-motion'
import { threeEffect } from '@humanspeak/svelte-motion/three'

animate.addEffect(threeEffect)
animate(mesh, { x: 1.5, rotateY: 360 }, { type: 'spring' })
import { animate } from '@humanspeak/svelte-motion'
import { threeEffect } from '@humanspeak/svelte-motion/three'

animate.addEffect(threeEffect)
animate(mesh, { x: 1.5, rotateY: 360 }, { type: 'spring' })

Register at module scope rather than from a component. The effect registry is global and does not reference-count, so removing threeEffect when one component unmounts would unregister it for every other component still animating a mesh. See custom effects for the full rule.

mode · live running open
// three-effect rotateY 0.00 rad · progress 0.00
mesh + shader uniform frame.preRender

Click Move again to move back, scrub the progress slider in either direction to apply or undo the ripple, or click Reset to restore the initial mesh and shader state immediately.

Supported subjects and values

The adapter claims the following Three.js-shaped subjects without importing three itself:

SubjectAnimated values
Object3D (meshes, cameras, lights)x, y, z, rotateX, rotateY, rotateZ, scaleX, scaleY, scaleZ, and scale
MaterialsDirect properties and colour keys such as color, emissive, and specular
Uniforms objectsAny { key: { value } } entry, including vector components such as offsetX
TSL uniform nodesUniform-node values assigned to slots such as material.colorNode

Rotation shorthands — rotateX, rotateY, and rotateZ — are in degrees. They are converted to radians on write, like DOM rotate, so a full turn is 360, not Math.PI * 2.

Shader uniforms

Bind a MotionValue directly to a uniforms object when you want to control it independently from the mesh:

import { animate, motionValue } from '@humanspeak/svelte-motion'
import { threeEffect } from '@humanspeak/svelte-motion/three'

const progress = motionValue(0)
const uniforms = { progress: { value: 0 } }
const unbind = threeEffect(uniforms, { progress })

animate(progress, 1, { duration: 1 })

// When the integration is no longer active:
unbind()
progress.destroy()
import { animate, motionValue } from '@humanspeak/svelte-motion'
import { threeEffect } from '@humanspeak/svelte-motion/three'

const progress = motionValue(0)
const uniforms = { progress: { value: 0 } }
const unbind = threeEffect(uniforms, { progress })

animate(progress, 1, { duration: 1 })

// When the integration is no longer active:
unbind()
progress.destroy()

Frame timing

threeEffect writes values in frame.preRender. Schedule the Three.js render loop with frame.render so every object transform and uniform update lands before the frame is drawn.

import { cancelFrame, frame } from '@humanspeak/svelte-motion'

const render = () => renderer.render(scene, camera)
frame.render(render, true)

// On cleanup, stop the render loop and release any values you bound.
// Leave the effect registered: the registry is global and shared.
cancelFrame(render)
unbind()
import { cancelFrame, frame } from '@humanspeak/svelte-motion'

const render = () => renderer.render(scene, camera)
frame.render(render, true)

// On cleanup, stop the render loop and release any values you bound.
// Leave the effect registered: the registry is global and shared.
cancelFrame(render)
unbind()

Related


Based on Motion 13.2’s motion/three.