threlte logo
@threlte/extras

<Portal>

A component that renders its children as children of an object that can exist anywhere in your Threlte application. You can use the prop id to render into a <PortalTarget>.

Although Portals are extremely helpful in certain situations, it can be hard to reason about them at times. It’s recommended to use them sparingly.

You might not need <Portal>

Some objects such as the THREE.DirectionalLightHelper need to be added to the scene itself to be functional.

In this case, the portal target is easily accessible, and passing the scene object to the <T> component’s attach property can accomplish everything we need.

<script lang="ts">
  import { Canvas } from '@threlte/core'
  import Scene from './Scene.svelte'
</script>

<div>
  <Canvas>
    <Scene />
  </Canvas>
</div>

<style>
  div {
    height: 100%;
    background-color: rgb(47 125 198 / 0.2);
  }
</style>
<script lang="ts">
  import { T, useThrelte } from '@threlte/core'
  import { Grid, OrbitControls, TransformControls } from '@threlte/extras'

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

<T.PerspectiveCamera
  position={[10, 10, 10]}
  makeDefault
  fov={30}
>
  <OrbitControls enableZoom={false} />
</T.PerspectiveCamera>

<Grid />

<!-- Red main light -->
<T.DirectionalLight
  color="#FE3D00"
  intensity={1}
  position={[1.5, 2, 0.5]}
>
  {#snippet children({ ref })}
    <T.DirectionalLightHelper
      attach={scene}
      args={[ref]}
    >
      {#snippet children({ ref: helperA })}
        <TransformControls
          object={ref}
          onobjectChange={() => helperA.update()}
        />
      {/snippet}
    </T.DirectionalLightHelper>
  {/snippet}
</T.DirectionalLight>

<!-- Blue rim light -->
<T.DirectionalLight
  intensity={0.5}
  color="#2F7DC6"
  position={[-1, -2, 1]}
>
  {#snippet children({ ref })}
    <T.DirectionalLightHelper
      attach={scene}
      args={[ref]}
    >
      {#snippet children({ ref: helperB })}
        <TransformControls
          object={ref}
          onobjectChange={() => helperB.update()}
        />
      {/snippet}
    </T.DirectionalLightHelper>
  {/snippet}
</T.DirectionalLight>

<T.Mesh position.y={0.5}>
  <T.SphereGeometry />
  <T.MeshStandardMaterial color="white" />
</T.Mesh>

Rendering to a <PortalTarget>

For more complex cases where it’s hard to query the target, using <Portal> may be an easier solution.

You can define where a <Portal> should render its children by using the component <PortalTarget>.

<script lang="ts">
  import { Canvas } from '@threlte/core'
  import Scene from './Scene.svelte'
</script>

<div>
  <Canvas>
    <Scene />
  </Canvas>
</div>

<style>
  div {
    height: 100%;
    background-color: rgb(47 125 198 / 0.2);
  }
</style>
<script lang="ts">
  import { T, useTask } from '@threlte/core'
  import { Grid, OrbitControls, Portal, PortalTarget } from '@threlte/extras'
  import { MathUtils } from 'three'

  let posX = $state(Math.sin(Date.now() / 1000) * 4)

  useTask(() => {
    posX = Math.sin(Date.now() / 1000) * 4
  })
</script>

<T.PerspectiveCamera
  position={[10, 10, 10]}
  makeDefault
  fov={30}
>
  <OrbitControls
    maxPolarAngle={85 * MathUtils.DEG2RAD}
    minPolarAngle={20 * MathUtils.DEG2RAD}
    maxAzimuthAngle={45 * MathUtils.DEG2RAD}
    minAzimuthAngle={-45 * MathUtils.DEG2RAD}
    enableZoom={false}
  />
</T.PerspectiveCamera>

<Grid />

<T.DirectionalLight position={[5, 10, 3]} />

<T.Object3D
  position.x={posX}
  position.y={0.5}
>
  <PortalTarget id="trail" />
</T.Object3D>

<Portal id="trail">
  <T.Mesh>
    <T.BoxGeometry />
    <T.MeshStandardMaterial color="#FE3D00" />
  </T.Mesh>

  <T.Group position.y={1}>
    <PortalTarget id="top" />
  </T.Group>
</Portal>

<Portal id="top">
  <T.Mesh>
    <T.BoxGeometry />
    <T.MeshStandardMaterial color="#2F7DC6" />
  </T.Mesh>
</Portal>

Component Signature

Props

name
type
required
default
description

id
string
no
"default"
The id of the portal to render into.