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 either provide an object that will be the parent of the children or 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.

Examples

Render Helper Objects

Some objects such as the THREE.DirectionalLightHelper need to be added to the scene instead of the light itself or another parent to be functional. We can use the <Portal> component for that.

<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, Portal, TransformControls } from '@threlte/extras'
  import type { DirectionalLightHelper } from 'three'

  const { scene } = useThrelte()

  let helperA: DirectionalLightHelper
  let helperB: DirectionalLightHelper
</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 })}
    <TransformControls
      object={ref}
      onobjectChange={() => {
        if (!helperA) return
        helperA.update()
      }}
    />
    <Portal object={scene}>
      <T.DirectionalLightHelper
        args={[ref]}
        bind:ref={helperA}
      />
    </Portal>
  {/snippet}
</T.DirectionalLight>

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

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

Render to a <PortalTarget>

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 = 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.

object
THREE.Object3D
no