@threlte/extras
transitions
Experimental
The plugin transitions
uses Svelte internals. Changes to the runtime of Svelte may break this
plugin. If you encounter any issues, please open an issue on
GitHub. It’s recommended to lock the version of Svelte
to a specific version.
The plugin transitions
enables Svelte-like
transitions
on Threlte components.
{#if visible}
<T.Mesh transition={scale({ duration: 400 })} />
{/if}
<script>
import { Canvas } from '@threlte/core'
import Scene from './Scene.svelte'
import { Checkbox, Pane } from 'svelte-tweakpane-ui'
import { Suspense } from '@threlte/extras'
let red = $state(true)
let blue = $state(true)
</script>
<Pane
title="Transitions"
position="fixed"
>
<Checkbox
bind:value={red}
label="Toggle Red"
/>
<Checkbox
bind:value={blue}
label="Toggle Blue"
/>
</Pane>
<div>
<Canvas>
<Suspense>
<Scene
{red}
{blue}
/>
</Suspense>
</Canvas>
</div>
<style>
:global(body) {
margin: 0;
}
div {
width: 100%;
height: 100%;
}
</style>
<script lang="ts">
import { T } from '@threlte/core'
import { Environment, Grid, OrbitControls, SoftShadows, transitions } from '@threlte/extras'
import { fade, scale } from './transitions'
transitions()
let { red, blue }: { red: boolean; blue: boolean } = $props()
</script>
{#if red}
<T.Mesh
castShadow
transition={scale(0)}
position.y={1}
position.x={-1.5}
>
<T.SphereGeometry />
<T.MeshStandardMaterial
transparent
color="red"
/>
</T.Mesh>
{/if}
{#if blue}
<T.Mesh
castShadow
position.y={1}
position.x={1.5}
>
<T.SphereGeometry />
<T.MeshToonMaterial
transparent
transition={fade()}
color="blue"
/>
</T.Mesh>
{/if}
<!-- Environment -->
<SoftShadows />
<Environment url="/textures/equirectangular/hdr/shanghai_riverside_1k.hdr" />
<!-- Camera -->
<T.PerspectiveCamera
makeDefault
position={[0, 3, 10]}
fov={30}
>
<OrbitControls
enableDamping
target={[0, 0.8, 0]}
enableZoom={false}
enablePan={false}
/>
</T.PerspectiveCamera>
<!-- Lights -->
<T.DirectionalLight
position={[10, 10, 10]}
castShadow
intensity={Math.PI / 2}
/>
<T.AmbientLight intensity={0.1} />
<!-- Floor -->
<Grid
sectionColor="#374668"
cellColor="#374668"
/>
<T.Mesh
receiveShadow
position.y={-0.01}
scale={20}
rotation.x={-Math.PI / 2}
>
<T.PlaneGeometry />
<T.MeshStandardMaterial color="#0F141F" />
</T.Mesh>
import { isInstanceOf } from '@threlte/core'
import { createTransition } from '@threlte/extras'
import { cubicOut } from 'svelte/easing'
export const fade = (opacity = 0) => {
return createTransition((ref: any) => {
if (!isInstanceOf(ref, 'Material')) return
if (!ref.transparent) {
ref.transparent = true
ref.needsUpdate = true
}
return {
duration: 600,
tick: (t: number) => {
ref.opacity = t * (1 - opacity)
},
easing: cubicOut
}
})
}
import { isInstanceOf } from '@threlte/core'
import { createTransition } from '@threlte/extras'
import { cubicOut } from 'svelte/easing'
import { mapLinear } from 'three/src/math/MathUtils.js'
export const fly = (options: { x?: number; y?: number; z?: number }) => {
return createTransition((ref) => {
if (!isInstanceOf(ref, 'Object3D')) return
return {
duration: 600,
tick(t) {
const x = mapLinear(t, 0, 1, options.x ?? 0, 0)
const y = mapLinear(t, 0, 1, options.y ?? 0, 0)
const z = mapLinear(t, 0, 1, options.z ?? 0, 0)
ref.position.set(x, y, z)
},
easing: cubicOut
}
})
}
export * from './fade'
export * from './fly'
export * from './scale'
import { isInstanceOf } from '@threlte/core'
import { createTransition } from '@threlte/extras'
import { cubicOut } from 'svelte/easing'
import { mapLinear } from 'three/src/math/MathUtils.js'
export const scale = (scale: number) => {
return createTransition((ref) => {
if (!isInstanceOf(ref, 'Object3D')) return
return {
duration: 600,
tick(t) {
ref.scale.setScalar(mapLinear(t, 0, 1, scale, 1))
},
easing: cubicOut
}
})
}
Usage
To use Threlte transitions, you need to inject the
plugin first via invoking transitions()
. All
child <T>
components will then accept the transition properties in
, out
and
transition
.
createTransition
Threlte Transitions use regular Svelte
transitions under the hood and
therefore provide a similar API. The function createTransition
is used to
conveniently create a transition.
To create a transition, you need to provide a function which accepts a reference
to the object referenced by the <T>
component and returns an object with the
following properties:
duration
: The duration of the transition in milliseconds.tick
: A function that is called on every tick of the transition.easing
(optional): The easing function to use.delay
(optional): The delay of the transition in milliseconds.
import { isInstanceOf } from '@threlte/core'
import { createTransition } from '@threlte/extras'
import { cubicOut } from 'svelte/easing'
const fade = createTransition((ref) => {
// Only apply the transition to materials
if (!isInstanceOf(ref, 'Material')) return
// Make the material transparent if it's not already
if (!ref.transparent) {
ref.transparent = true
ref.needsUpdate = true
}
return {
tick(t) {
// t is [0, 1]
ref.opacity = t
},
easing: cubicOut,
duration: 400,
delay: 100
}
})
The transition fade
can now be applied to all <T>
components that
instantiate classes extending THREE.Material
like THREE.MeshBasicMaterial
or
THREE.MeshStandardMaterial
:
<T.MeshStandardMaterial transition={fade} />
Transition Directions
Run a transition only when the component mounts:
<T.MeshStandardMaterial in={fade} />
Run a transition only when the component unmounts:
<T.MeshStandardMaterial out={fade} />
Run a transition when the component mounts or unmounts:
<T.MeshStandardMaterial transition={fade} />
To react on different transition directions in the same transition, you can use
the direction
parameter:
import { createTransition } from '@threlte/extras'
// direction is 'in', 'out' or 'both'
const fly = createTransition((ref, { direction }) => {
// …
})
Transition Parameters
To make reusing transitions throughout your application easier, make
createTransition
the return value of a function that accepts parameters:
import { isInstanceOf } from '@threlte/core'
import { createTransition } from '@threlte/extras'
const scale = (duration: number) => {
return createTransition((ref) => {
// Only apply the transition to objects
if (!isInstanceOf(ref, 'Object3D')) return
return {
tick(t) {
ref.scale.setScalar(t)
},
duration
}
})
}
The transition can now be used like this:
<T.Mesh transition={scale(400)} />
Transition Events
Similar to Svelte transitions, Threlte transitions also emit events:
{#if visible}
<T.Mesh
{geometry}
{material}
transition={fade}
onintrostart={() => console.log('intro started')}
onoutrostart={() => console.log('outro started')}
onintroend={() => console.log('intro ended')}
onoutroend={() => console.log('outro ended')}
/>
{/if}
Global Transitions
Transitions are local by
default. Local
transitions only play when the block they belong to is created or destroyed, not
when parent blocks are created or destroyed. Threlte offers a function global
that marks a transition as global.
<script>
import { global } from '@threlte/extras'
</script>
{#if x}
{#if y}
<T.Mesh transition={global(scale(400))} />
{/if}
{/if}
TypeScript
Prop Types
By default, the transitions
plugin does not add any prop types to the <T>
component. You can however extend the types of the <T>
component by defining
the Threlte.UserProps
type in your ambient type definitions. In a typical
SvelteKit application, you can find these in
src/app.d.ts
. The transitions
plugin exports the TransitionsProps
type which you can use as shown below:
import type { TransitionsProps } from '@threlte/extras'
declare global {
namespace Threlte {
interface UserProps extends TransitionsProps {}
}
}
export {}
Now all relevant properties on <T>
components will be type safe.
<script>
import { transitions } from '@threlte/extras'
transitions()
</script>
<T.Mesh
transition={scale(400)}
onintrostart={() => console.log('intro started')}
/>