Skip to content

<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="z-1 relative 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.

Props

name
type
required
description

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