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

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.