Getting Started
Your First Scene
Understanding Svelte and Three.js is important when using Threlte. If you’re new to Svelte, start with the official tutorial. If you’re new to Three.js, check the official documentation and guides.
Structuring your app
First we create a new Svelte file, App.svelte, where we import <Canvas>.
<script>
import { Canvas } from '@threlte/core'
import Scene from './Scene.svelte'
</script>
<Canvas>
<Scene />
</Canvas>
The <Canvas> component is the root of a Threlte app: it creates a renderer and a default camera,
sets sensible defaults (like antialiasing and color management),
and provides Threlte’s runtime context. To access this runtime context, we’ll need to create a separate component
called Scene.svelte and including it in our App.svelte file.
Creating objects
At this point we’re just looking at a blank screen. Let’s add a simple cube to it.
In Scene.svelte, we import the <T> component, which is
the main building block of your Threlte application. It’s a thin, generic wrapper for any Three.js class.
Here we’ll create a
THREE.Mesh with
a THREE.BoxGeometry and
a THREE.MeshBasicMaterial.
We should now see a white cube on a transparent background.
<script>
import { T } from '@threlte/core'
</script>
<T.Mesh>
<T.BoxGeometry />
<T.MeshBasicMaterial />
</T.Mesh>
attach
Behind the scenes we’re using the property attach available on <T> to attach an object to a property of
its parent. Binding geometries to the property geometry and materials to the property material is a common
pattern so Threlte takes care of it for you.
Learn more
We’re using the property attach available on <T> to
attach an object to a property of its parent. In our case we’re attaching the underlying Three.js
object of <T.BoxGeometry> to the property geometry of the <T.Mesh> component. We’re also attaching
the underlying Three.js object of <T.MeshBasicMaterial> to the property material of the <T.Mesh> component.
<script>
import { T } from '@threlte/core'
</script>
<T.Mesh>
<T.BoxGeometry attach="geometry" />
<T.MeshBasicMaterial attach="material" />
</T.Mesh>
Binding geometries to the property geometry and materials to the property material is a common
pattern so Threlte will take care of it. It checks for the properties isMaterial and isGeometry on
the underlying Three.js object and attaches it to the correct property.
Three.js equivalent
// creating the objects
const geometry = new THREE.BoxGeometry()
const material = new THREE.MeshBasicMaterial()
const mesh = new THREE.Mesh()
// "attaching" the objects
mesh.geometry = geometry
mesh.material = material
Modifying objects
That cube is still a bit boring. Let’s give it some color,
scale it up, and move it slightly upward. We can do this by
passing props to <T>.
<script>
import { T } from '@threlte/core'
</script>
<T.Mesh position.y={1}>
<T.BoxGeometry args={[1, 2, 1]} />
<T.MeshBasicMaterial color="hotpink" />
</T.Mesh>
Threlte automatically generates props for <T> based on the underlying Three.js object. This means you can easily guess most <T> props
based on the Three.js docs for the class you are using.
Three.js equivalent
const mesh = new THREE.Mesh()
const geometry = new THREE.BoxGeometry(1, 2, 1)
const material = new THREE.MeshBasicMaterial()
mesh.position.y = 1
material.color.set('hotpink')
The special args prop we use in <T.BoxGeometry> corresponds to the
object’s constructor arguments. Props interpreted from the underlying Three.js
object are called auto props, like color in our <T.MeshBasicMaterial>.
Leveraging Threlte’s Pierced Props you can directly assigned to attributes
of props like position.y in our <T.Mesh>.
Learn more
args
In Three.js objects are classes that are instantiated. These classes can
receive one-time constructor arguments (new THREE.SphereGeometry(1, 32)). In
Threlte, constructor arguments are always passed as an array via the prop
args. If args change later on, the object must naturally get reconstructed
from scratch!
Auto props
For all other props, Threlte tries to automatically interpret props passed to <T> component.
Step 1. Find Properties - First, Threlte will try to find the property
on the underlying Three.js object based on the name of the prop. In our
example, color is a property of
THREE.MeshBasicMaterial.
Step 2. Try set Methods - Next, Threlte will look for a set method
on that property and use it to set the new value. In our example it will call
material.color.set('hotpink') to set the color of our material.
Step 3. Try setting the property directly - If there’s no set method,
it will try to set the property directly. In our example, this equated to
mesh.position.y = 1.
Step 4. Check for array values - When setting a property that accepts
more than one value (such as a THREE.Vector3: vec3.set(1, 2, 3)), we can
pass an array as a prop.
Step 5. Keep the prop type constant for the lifetime of the component - If the prop value changes, Threlte will try to set the property again. If the type of the prop value changes, Threlte won’t be able to reliably do that. For instance the type of the value of a variable that is used as a prop should not change from a single number to an array of numbers.
Pierced props
Because the property position of our THREE.Mesh is a THREE.Vector3, it
also has x, y and z properties which we can set directly via
dot-notation, we call this Pierced Props.
Primitive values
From a performance perspective, it’s often better to use pierced props because primitive prop values can safely be compared for equality. This means that if the value of a prop doesn’t change, Threlte will skip any updates to the underlying Three.js object.
Constant prop types
The type of an inferred prop (or “auto prop”) must be constant. This means that the type of a prop must not change for the lifetime of the component. For instance you can’t use a variable as a prop that is an array of numbers and then later on change the value of that variable to a single number. This is considered a type change and therefore not allowed.
Pointing the camera
We now want to view our cube with some perspective. To manipulate the default camera, let’s add the following:
<script>
import { T } from '@threlte/core'
</script>
<T.PerspectiveCamera
makeDefault
position={[10, 10, 10]}
oncreate={(ref) => {
ref.lookAt(0, 1, 0)
}}
/>
<T.Mesh position.y={1}>
<T.BoxGeometry args={[1, 2, 1]} />
<T.MeshBasicMaterial color="hotpink" />
</T.Mesh>
We’re again using the <T> component to create a THREE.PerspectiveCamera.
We’re also passing a makeDefault prop which will make this camera the default camera of our application.
The renderer now uses this camera to render our scene.
Events
Threlte supports listening to certain events on <T/> components. Here, we use the create
event to get a reference to the underlying Three.js object as soon as it’s created and use the method lookAt to look at the cube.
Enabling interactivity
Let’s say we want to scale our cube as soon as we hover over it. We first have to import the
interactivity plugin from
@threlte/extras and invoke it in our Scene.svelte file.
The interactivity plugin lets us add interaction event listeners like pointerenter and
pointerleave to our <T> components. In these event handlers we’ll update the value of a Spring from svelte/motion
and use its .current value to set the scale property of the <T.Mesh> component.
<script>
import { T } from '@threlte/core'
import { interactivity } from '@threlte/extras'
import { Spring } from 'svelte/motion'
interactivity()
const scale = new Spring(1)
</script>
<T.PerspectiveCamera
makeDefault
position={[10, 10, 10]}
oncreate={(ref) => {
ref.lookAt(0, 1, 0)
}}
/>
<T.Mesh
position.y={1}
scale={scale.current}
onpointerenter={() => {
scale.target = 1.5
}}
onpointerleave={() => {
scale.target = 1
}}
>
<T.BoxGeometry args={[1, 2, 1]} />
<T.MeshBasicMaterial color="hotpink" />
</T.Mesh>
Automatic vector & scalar detection
You might have noticed that we’re only passing a single number to the prop scale on <T.Mesh>. Threlte automatically
figures out whether you are passing an array or a number and uses the appropriate underlying Three.js method.
Learn more
The component <T> will first look for a property setScalar on the underlying Three.js object and use that method if
only a single number is passed. This is equivalent to calling scale.setScalar(scale.current).
Realtime variables
When working with realtime apps where variables e.g. position and rotation change constantly, an easy way observe the values is with live expressions.
Adding animation
Let’s add some motion to our cube. We will use Threlte’s useTask hook to tap
into Threlte’s unified frame loop and run a function on every frame. We again use a Pierced Prop to let the
cube rotate around its y-axis.
<script>
import { T, useTask } from '@threlte/core'
import { interactivity } from '@threlte/extras'
import { Spring } from 'svelte/motion'
interactivity()
const scale = new Spring(1)
let rotation = $state(0)
useTask((delta) => {
rotation += delta
})
</script>
<T.PerspectiveCamera
makeDefault
position={[10, 10, 10]}
oncreate={(ref) => {
ref.lookAt(0, 1, 0)
}}
/>
<T.Mesh
rotation.y={rotation}
position.y={1}
scale={scale.current}
onpointerenter={() => {
scale.target = 1.5
}}
onpointerleave={() => {
scale.target = 1
}}
>
<T.BoxGeometry args={[1, 2, 1]} />
<T.MeshBasicMaterial color="hotpink" />
</T.Mesh>
useTask registers a callback that will be invoked on every frame. The callback receives the time delta since the last frame as an argument. We use
the delta to update the rotation
independent of the frame rate
– the cube will rotate at the same speed regardless of the frame rate.
Adjusting the lighting
We’re almost done. Let’s add some shading to our cube and a light source. We’ll use a
THREE.MeshStandardMaterial on our cube and a THREE.DirectionalLight to illuminate our scene.
<script>
import { T, useTask } from '@threlte/core'
import { interactivity } from '@threlte/extras'
import { Spring } from 'svelte/motion'
interactivity()
const scale = new Spring(1)
let rotation = $state(0)
useTask((delta) => {
rotation += delta
})
</script>
<T.PerspectiveCamera
makeDefault
position={[10, 10, 10]}
oncreate={(ref) => {
ref.lookAt(0, 1, 0)
}}
/>
<T.DirectionalLight position={[0, 10, 10]} />
<T.Mesh
rotation.y={rotation}
position.y={1}
scale={scale.current}
onpointerenter={() => {
scale.target = 1.5
}}
onpointerleave={() => {
scale.target = 1
}}
>
<T.BoxGeometry args={[1, 2, 1]} />
<T.MeshStandardMaterial color="hotpink" />
</T.Mesh>
Casting shadows
We would like our cube to cast a shadow. To do so, we need a floor for it to cast a shadow on,
so we add a new <T.Mesh> but this time with <T.CircleGeometry>. To enable shadows, we need to
set castShadow on both the light and our cube, and set receiveShadow on our new floor:
<script>
import { T, useTask } from '@threlte/core'
import { interactivity } from '@threlte/extras'
import { Spring } from 'svelte/motion'
interactivity()
const scale = new Spring(1)
let rotation = $state(0)
useTask((delta) => {
rotation += delta
})
</script>
<T.PerspectiveCamera
makeDefault
position={[10, 10, 10]}
oncreate={(ref) => {
ref.lookAt(0, 1, 0)
}}
/>
<T.DirectionalLight
position={[0, 10, 10]}
castShadow
/>
<T.Mesh
rotation.y={rotation}
position.y={1}
scale={scale.current}
onpointerenter={() => {
scale.target = 1.5
}}
onpointerleave={() => {
scale.target = 1
}}
castShadow
>
<T.BoxGeometry args={[1, 2, 1]} />
<T.MeshStandardMaterial color="hotpink" />
</T.Mesh>
<T.Mesh
rotation.x={-Math.PI / 2}
receiveShadow
>
<T.CircleGeometry args={[4, 40]} />
<T.MeshStandardMaterial color="white" />
</T.Mesh>
Conclusion
Congratulations, you’ve just created your first Three.js scene with Threlte! It includes important Three.js and Threlte concepts and should give you a good starting point for your first Threlte project.