threlte logo
@threlte/core

<T>

The component <T> provides the means to use any three.js export as a Svelte component. It does this by leveraging the rigid three.js naming and object property structure to add and remove objects to and from the scene graph, attach objects to parent object properties or add event listeners.

<T> is the main building block of any Threlte application. Components available in '@threlte/extras' are built on top of <T> and may provide a more convenient API for specific three.js classes.

Usage Types

Primer on Terminology

// Class definition:
class Mesh extends THREE.Object3D {
  constructor(geometry, material) {
    /* … */
  }
}

// Creating a class instance:
const mesh = new Mesh()

// Creating a class instance with constructor arguments:
const mesh = new Mesh(geometry, material)

There are two ways to use <T>:

  • Use dot-notation to use any three.js class available at Three.js’ main namespace 'three':
<script>
  import { T } from '@threlte/core'
</script>

<T.Mesh />
  • Pass the property is to <T>:
<script>
  import { T } from '@threlte/core'
  import { Mesh } from 'three'
</script>

<T is={Mesh} />

Both ways are equivalent and can be used interchangeably. The latter is more explicit and allows you to use any class definition (even if it’s not exported from Three.js’ main namespace 'three'), object instance or virtually any other value.

The next section will discuss both ways in more detail.

Dot-Notation

Any three.js class available at Three.js’ main namespace 'three' can be used as a component with full type-safety. The name of the import is the same as the name of the component. For example, the class THREE.Mesh can be used with the component <T.Mesh>. Let’s take a look at a simple example:

<script>
  import { T } from '@threlte/core'
</script>

<T.Mesh>
  <T.BoxGeometry />
  <T.MeshBasicMaterial />
</T.Mesh>

Let’s break this down:

  • The component <T.Mesh> creates an instance of THREE.Mesh which is automatically added to the scene graph.
  • The component <T.BoxGeometry> creates an instance of THREE.BoxGeometry which is automatically “attached” to the property geometry of the parent THREE.Mesh.
  • The component <T.MeshBasicMaterial> creates an instance of THREE.MeshBasicMaterial which is automatically “attached” to the property material of the parent THREE.Mesh.

Extend the default component catalogue

If you want to use a class that is not available at Three.js’ main namespace 'three' with dot-notation, you can extend the default component catalogue. Be aware that components used this way do not offer type-safety. Once extended, the updated catalogue is available to all components in your application.

Camera.svelte
<script>
  import { T, extend, useThrelte } from '@threlte/core'
  import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'

  extend({
    OrbitControls
  })

  const { renderer } = useThrelte()
</script>

<T.PerspectiveCamera makeDefault>
  {#snippet children({ ref })}
    <T.OrbitControls args={[ref, renderer.domElement]} />
  {/snippet}
</T.PerspectiveCamera>

Property is

To explicitly pass a class definition to the component <T>, use the property is. Let’s take a look at the same example as above but using the property is:

<script>
  import { T } from '@threlte/core'
  import { Mesh, BoxGeometry, MeshBasicMaterial } from 'three'
</script>

<T is={Mesh}>
  <T is={BoxGeometry} />
  <T is={MeshBasicMaterial} />
</T>

The two examples are equivalent and can be used interchangeably.

The “vanilla” Three.js equivalent of both examples would be:

import { Mesh, BoxGeometry, MeshBasicMaterial } from 'three'

const mesh = new THREE.Mesh(new THREE.BoxGeometry(), new THREE.MeshBasicMaterial())
scene.add(mesh)

Using the property is comes in handy when using classes that are not exported from Three.js’ main namespace 'three', such as the OrbitControls class:

<script>
  import { T } from '@threlte/core'
  import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
</script>

<T is={OrbitControls} />

What’s happening under the hood?

If a class definition such as THREE.Mesh is provided to the property is, it creates an instance of that class which we call ref – the component’s reference to the three.js object:

<T is={Mesh} />

If a class instance (such as new THREE.Mesh()) or any other value is provided, the component uses this value as-is:

<script>
  const mesh = new Mesh()
</script>

<T is={mesh} />

Depending on the is property value types, Threlte makes certain assumptions:

  • If the value passed to is is extending THREE.Object3D it’s added to the scene graph.
  • If the value passed to is is a disposable, it’s disposed onDestroy or whenever the args change and a new ref is created.
  • If the value passed to is is has a property addEventListener, you can add event callbacks.
  • If the value passed to is is extending THREE.Camera, certain camera-related properties are available.

Props

The <T> component has a set of fixed props (namely args, is, attach, manual, makeDefault and dispose) that are used to set up the Three.js object. On top of that, you can use arbitrary props to reactively set any property of the underlying Three.js object.

Three.js object props

To understand how it works, let’s have a look at a simple example. Let’s say we want to render a simple cube. We can do this by using the <T> component:

<script>
  import { T } from '@threlte/core'
</script>

<T.Mesh>
  <T.BoxGeometry />
  <T.MeshBasicMaterial />
</T.Mesh>

Using automatic attach, the geometry as well as the material are assigned to the mesh. What if we want to change the color of the material to "red"? We can do this by using the color prop:

<script>
  import { T } from '@threlte/core'
</script>

<T.Mesh>
  <T.BoxGeometry />
  <T.MeshBasicMaterial color="red" />
</T.Mesh>

Keep in mind that this property is not hard-wired. We can use any property of the underlying Three.js object as a prop on the <T> component. For example, we can also set the position property of the THREE.Mesh:

<script>
  import { T } from '@threlte/core'
</script>

<T.Mesh position={[0, 1, 0]}>
  <T.BoxGeometry />
  <T.MeshBasicMaterial color="red" />
</T.Mesh>

Because the property position of a THREE.Mesh is a THREE.Vector3 the value we have to provide is what is passed to the set function of the THREE.Vector3 class. In this case, we pass an array of three numbers ([x, y, z]). Using an editor like VS Code, you benefit from type hints and auto-completion.

We only changed the y coordinate of the position, so we can use a pierced prop to only change the y coordinate:

<script>
  import { T } from '@threlte/core'
</script>

<T.Mesh position.y={1}>
  <T.BoxGeometry />
  <T.MeshBasicMaterial color="red" />
</T.Mesh>

This way, we get less updates of the underlying Three.js object because the value of the prop is a primitive value which can easily be compared to the previous value. Unfortunately with pierced props we miss out on the type hints and auto-completion.

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.

args

Three.js objects are class instances. When Instantiating these objects, they can receive one-time constructor arguments (new THREE.SphereGeometry(1, 32)). In Threlte, constructor arguments are always passed as an array via the property args. If args change later on, the object must naturally get reconstructed from scratch!

  • If a class definition such as THREE.BoxGeometry is provided to the property is, the property args is used to instantiate the class: <T is={BoxGeometry} args={[1, 2, 1]}> equals new BoxGeometry(1, 2, 1).

attach

Use attach to attach objects to other objects.

The following attaches a material to the material property of a mesh and a geometry to the geometry property:

<T.Mesh>
  <T.MeshBasicMaterial attach="material" />
  <T.BoxGeometry attach="geometry" />
</T.Mesh>

All materials receive attach="material", and all geometries receive attach="geometry" automatically. You do not strictly have to type it out!

  • The object referenced by the <T> component is “attached” to a parent objects property.
<script>
  import { MeshStandardMaterial } from 'three'
  export let texture
</script>

<T is={MeshStandardMaterial}>
  <!-- Attaches the texture to the property "map" of the parent material -->
  <T
    is={texture}
    attach="map"
  />
</T>
  • attach can be a dot-notated path to a nested parent property:
<T.DirectionalLight>
  <!--
    Attaches an instance of a THREE.OrthographicCamera
    to the property camera of the property shadow of the
    parent THREE.DirectionalLight
  -->
  <T.OrthographicCamera
    args={[-1, 1, 1, -1, 0.1, 100]}
    attach="shadow.camera"
  />
</T.DirectionalLight>
  • attach can also be a function which is called when the component is created. This function receives the parent, the closest upstream parent THREE.Object3D and the value inferred from the property is as arguments. It can return a function which is called whenever the component is destroyed or the args change and a new ref is created:
<T.DirectionalLight>
  <!--
    Attaches an instance of a THREE.OrthographicCamera
    to the property camera of the property shadow of the
    parent THREE.DirectionalLight
  -->
  <T.OrthographicCamera
    args={[-1, 1, 1, -1, 0.1, 100]}
    attach={({ ref, parent, parentObject3D }) => {
      console.log('attaching', ref, parent, parentObject3D)
      parent.shadow.camera = ref
      return () => {
        parent.shadow.camera = null
      }
    }}
  />
</T.DirectionalLight>
  • You may also pass an object3D instance to the attach prop. This allows you to attach the object to a specific parent object, essentially acting as a portal.
<T
  is={Mesh}
  attach={otherObject}
/>
  • To disable attaching, pass false. This is useful if you want to attach the object manually:
<T
  is={Mesh}
  attach={false}
/>

Camera Props

By default Threlte is responsive and will set up cameras properly on resize (aspect ratio etc). Cameras can be controlled manually by setting manual to true. This will opt out of projection matrix recalculation when the drawing area resizes or other camera-related properties change.

<T
  is={PerspectiveCamera}
  manual
/>

Use the property makeDefault to set a camera to the default rendering camera.

<T
  is={PerspectiveCamera}
  makeDefault
/>

A common mistake is to forget setting makeDefault. If you do not set a camera to be the default camera, the scene will not be rendered through this camera but through Threlte’s default camera.

Events

Object Events

Adding an event listener to a component will also add the corresponding event listener to the three.js class instance. The event will be forwarded and the native payload is available as the first argument to the event listener.

This will listen to the “change” event on the THREE.OrbitControls:

<T
  is={OrbitControls}
  onchange={(e) => console.log('change:', e)}
/>

Create Event

All <T> components also emit the create event when the underlying three.js class instance is created. This can be used to access the instance from the parent component or do tasks on the objects upon creation. The event handler is called with a reference to the object. You may return a cleanup callback. It will be invoked when the component unmounts or when the object is re-instantiated.

<T.PerspectiveCamera
  oncreate={(ref) => {
    // Look at the center
    ref.lookAt(0, 0, 0)

    return () => {
      // Do something when the camera is disposed
    }
  }}
/>

Snippet Props

The object referenced by the component is available as the snippet prop ref:

<T.PerspectiveCamera>
  {#snippet children({ ref: camera })}
    <!--
      The prop "ref" is used to reference the
      camera and instantiate the OrbitControls
    -->
    <T
      is={OrbitControls}
      args={[camera, renderer.domElement]}
    />
  {/snippet}
</T.PerspectiveCamera>

Bindings

The object referenced by the component is available as the binding ref:

<script>
  let camera = $state()
  $effect(() => {
    console.log(camera) // THREE.PerspectiveCamera
  })
</script>

<T.PerspectiveCamera bind:ref={camera} />

Extending the default component catalogue

By default when using the dot-notation to access Three.js objects (e.g. <T.Mesh>), Threlte will automatically import the corresponding class from the namespance 'three'. If you want to use a custom class or classes from Three.js that are available elsewhere (like OrbitControls), you can extend the default catalogue with the extend function:

<script>
  import { extend, T } from '@threlte/core'
  import { CustomMesh } from './MyCustomMesh.ts'
  import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'

  extend({
    CustomMesh
  })
</script>

<T.CustomMesh />
Be aware that in this case <T> can’t provide type-safety on properties and events. If you need type-safety, you can use the is property instead: <T is={CustomMesh} />. This way, Threlte can infer the type of the object and provide type-safety on properties and events.

Plugins

The component <T> can be extended in functionality with Threlte Plugins.