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