threlte logo
@threlte/extras

<View>

This is a port of drei’s <View/> component. It is used to display multiple scenes using only one canvas and one renderer. Our implemenation re-uses parts of the main context but creates new camera, scene, parent, DOM, cache and user contexts. This ensures you can use Threlte’s other components as per normal.

The following example is equivalent to three.js’s multiple elements example.

<script lang="ts">
  import { Canvas } from '@threlte/core'
  import { View } from '@threlte/extras'
  import Scene from './Scene.svelte'
  import * as THREE from 'three'
  import type { itemType } from './types'

  const items: itemType[] = []
  const geometries = [
    new THREE.BoxGeometry(1, 1, 1),
    new THREE.SphereGeometry(0.5, 12, 8),
    new THREE.DodecahedronGeometry(0.5),
    new THREE.CylinderGeometry(0.5, 0.5, 1, 12)
  ]
  for (let i = 0; i < 40; i++) {
    // add one random mesh to each scene
    const geometry = geometries[(geometries.length * Math.random()) | 0]!

    const material = new THREE.MeshStandardMaterial({
      color: new THREE.Color().setHSL(Math.random(), 1, 0.75, THREE.SRGBColorSpace),
      roughness: 0.5,
      metalness: 0,
      flatShading: true
    })
    items.push({ dom: undefined, geometry, material })
  }
</script>

<div
  id="container"
  class="bg-white"
>
  <div
    id="content"
    class="relative z-[1] h-full overflow-y-scroll"
  >
    {#each items as item, i}
      <div
        id="item"
        class="m-4 inline-block p-4 shadow-md"
      >
        <div
          bind:this={item.dom}
          class="h-[200px] w-[200px]"
        ></div>
        <div class="mt-2 text-[#888]">Scene {i + 1}</div>
      </div>
    {/each}
  </div>
  <div
    id="canvas"
    class="absolute top-0 h-full"
  >
    <Canvas>
      {#each items as item}
        <View dom={item.dom}>
          <Scene {...item} />
        </View>
      {/each}
    </Canvas>
  </div>
</div>

<style>
  div#container {
    height: 100%;
    background: white;
  }
  div#content {
    position: relative;
    z-index: 1;
    height: 100%;
    overflow-y: scroll;
  }
  div#item {
    margin: 1rem;
    display: inline-block;
    padding: 1rem;
    box-shadow:
      rgba(0, 0, 0, 0) 0px 0px 0px 0px,
      rgba(0, 0, 0, 0) 0px 0px 0px 0px,
      rgba(0, 0, 0, 0.1) 0px 4px 6px -1px,
      rgba(0, 0, 0, 0.1) 0px 2px 4px -2px;
  }
  div#item > div:first-child {
    height: 200px;
    width: 200px;
  }
  div#item > div:last-child {
    margin-top: 0.5rem;
    color: #888;
  }
</style>
<script lang="ts">
  import { T, useTask, useThrelte } from '@threlte/core'
  import { OrbitControls } from '@threlte/extras'
  import { Color } from 'three'

  let { geometry, material } = $props()

  const { scene } = useThrelte()
  let rotation = $state(0)

  scene.background = new Color(0xe0e0e0)

  useTask((delta) => {
    rotation += delta
  })
</script>

<T.PerspectiveCamera
  makeDefault
  position={[0, 0, 2]}
  fov={50}
  near={1}
  far={10}
>
  <OrbitControls
    minDistance={2}
    maxDistance={5}
    enablePan={false}
  />
</T.PerspectiveCamera>

<T.HemisphereLight args={[0xaaaaaa, 0x444444, 3]} />
<T.DirectionalLight
  args={[0xffffff, 1.5]}
  position={[1, 1, 1]}
/>

<T.Mesh rotation.y={rotation}>
  <T is={geometry} />
  <T is={material} />
</T.Mesh>
import type * as THREE from 'three'

export type geoTypes =
  | THREE.BoxGeometry
  | THREE.SphereGeometry
  | THREE.DodecahedronGeometry
  | THREE.CylinderGeometry

export type itemType = {
  dom: HTMLElement | undefined
  geometry: geoTypes
  material: THREE.MeshStandardMaterial
}

Under the hood, this component uses the renderer’s scissor-cut method. Three.js has documentation for when using scissor-cuts as the styling of your canvas has different effects on the renderering. You access the canvas to switch between the options via the useThrelte hook.

Component Signature

Props

name
type
required
description

dom
HTMLElement
no
The target DOM to scissor-cut and attach events. Does nothing if none given.