@threlte/flex
Examples
The package @threlte/flex is well suited for building responsive layouts.
Especially in an XR context, where using the DOM is (currently) not
possible you have to rely on
solutions that are native to WebGL.
The following examples show how to use the @threlte/flex package to build
responsive layouts in WebGL.
The left side is the CSS implementation, the right side is using @threlte/flex.
<script lang="ts">
import { Canvas } from '@threlte/core'
import { NoToneMapping } from 'three'
import { onMount } from 'svelte'
import Common from './examples/Common.svelte'
type Example = {
name: string
dom: ConstructorOfATypedSvelteComponent
threlte: ConstructorOfATypedSvelteComponent
}
let selected: string = $state('')
let components: Example[] = $state([])
const load = () => {
const modules = (import.meta as any).glob('./examples/*/*.svelte', { eager: true }) as Record<
string,
any
>
components = Object.entries(modules).reduce(
(acc, [key, module]) => {
const name = key.split('/')[2]
if (!name) throw new Error(`No name for ${key}`)
const isDom = key.includes('Dom')
const example = acc.find((e) => e.name === name)
if (example) {
if (isDom) {
example.dom = (module as any).default
} else {
example.threlte = (module as any).default
}
} else {
acc.push({
name,
dom: isDom ? (module as any).default : undefined,
threlte: isDom ? undefined : (module as any).default
})
}
return acc
},
[] as unknown as typeof components
)
selected ||= components[0]!.name
}
onMount(load)
let example = $derived(components.find((e) => e.name === selected))
$effect(() => {
if (selected.length) sessionStorage.selected = selected
})
</script>
<svelte:window
onkeydown={(event) => {
if (event.key === 'ArrowLeft') {
const index = components.findIndex((e) => e.name === selected)
selected = components[index - 1]?.name || selected
} else if (event.key === 'ArrowRight') {
const index = components.findIndex((e) => e.name === selected)
selected = components[index + 1]?.name || selected
}
}}
/>
{#if example}
<div class="example-view split-view">
<div class="dom">
<div>
<example.dom />
</div>
</div>
<div class="threlte">
<Canvas toneMapping={NoToneMapping}>
<Common />
<example.threlte />
</Canvas>
</div>
</div>
{/if}
<nav>
<select bind:value={selected}>
{#each components as { name } (name)}
<option value={name}>{name}</option>
{/each}
</select>
</nav>
<style>
:global(body) {
margin: 0;
}
nav {
padding: 20px;
position: relative;
}
.split-view {
width: 100vw;
display: flex;
flex-direction: row;
}
.split-view > * {
flex: 1;
}
.split-view > :nth-child(2) {
border-left: 1px solid #404550;
}
.example-view {
position: absolute;
top: 0;
height: 100vh;
}
.threlte {
height: 100%;
}
.dom {
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
}
</style>
<script lang="ts">
import { T } from '@threlte/core'
import { MathUtils } from 'three'
interface Props {
size?: number
thickness?: number
arrows?: boolean
hideX?: boolean
hideY?: boolean
hideZ?: boolean
}
let {
size = 1,
thickness = 0.02,
arrows = false,
hideX = false,
hideY = false,
hideZ = false
}: Props = $props()
let showAny = $derived(!hideX || !hideY || !hideZ)
</script>
{#if showAny}
<T.Mesh>
<T.SphereGeometry args={[thickness * 2]} />
<T.MeshBasicMaterial color="white" />
</T.Mesh>
{/if}
<!-- X Axis -->
{#if !hideX}
<T.Mesh position.x={size / 2}>
<T.CylinderGeometry
args={[thickness, thickness, size]}
oncreate={(ref) => {
ref.rotateZ(90 * MathUtils.DEG2RAD)
}}
/>
<T.MeshBasicMaterial color="red" />
{#if arrows}
<T.Mesh position.x={size / 2 + thickness * 3}>
<T.ConeGeometry
args={[thickness * 3, thickness * 6]}
oncreate={(ref) => {
ref.rotateZ(-90 * MathUtils.DEG2RAD)
}}
/>
<T.MeshBasicMaterial color="red" />
</T.Mesh>
{/if}
</T.Mesh>
{/if}
<!-- Y Axis -->
{#if !hideY}
<T.Mesh position.y={size / 2}>
<T.CylinderGeometry args={[thickness, thickness, size]} />
<T.MeshBasicMaterial color="green" />
{#if arrows}
<T.Mesh position.y={size / 2 + thickness * 3}>
<T.ConeGeometry args={[thickness * 3, thickness * 6]} />
<T.MeshBasicMaterial color="green" />
</T.Mesh>
{/if}
</T.Mesh>
{/if}
<!-- Z Axis -->
{#if !hideZ}
<T.Mesh position.z={size / 2}>
<T.CylinderGeometry
args={[thickness, thickness, size]}
oncreate={(ref) => {
ref.rotateX(90 * MathUtils.DEG2RAD)
}}
/>
<T.MeshBasicMaterial color="blue" />
{#if arrows}
<T.Mesh position.z={size / 2 + thickness * 3}>
<T.ConeGeometry
args={[thickness * 3, thickness * 6]}
oncreate={(ref) => {
ref.rotateX(90 * MathUtils.DEG2RAD)
}}
/>
<T.MeshBasicMaterial color="blue" />
</T.Mesh>
{/if}
</T.Mesh>
{/if}
<script lang="ts">
import { T } from '@threlte/core'
interface Props {
color?: string
height?: number
width?: number
depth?: number
}
let { color = 'white', height = 1, width = 1, depth = 0 }: Props = $props()
</script>
<T.Mesh
position.z={depth * 20}
renderOrder={depth}
>
<T.PlaneGeometry args={[width, height]} />
<T.MeshBasicMaterial
{color}
transparent
opacity={0.5}
/>
</T.Mesh>
.red {
background-color: color-mix(in srgb, red 50%, transparent 50%);
}
.blue {
background-color: color-mix(in srgb, blue 50%, transparent 50%);
}
.green {
background-color: color-mix(in srgb, green 50%, transparent 50%);
}
.yellow {
background-color: color-mix(in srgb, yellow 50%, transparent 50%);
}
.purple {
background-color: color-mix(in srgb, purple 50%, transparent 50%);
}
.orange {
background-color: color-mix(in srgb, orange 50%, transparent 50%);
}
.pink {
background-color: color-mix(in srgb, pink 50%, transparent 50%);
}
.brown {
background-color: color-mix(in srgb, brown 50%, transparent 50%);
}
.fuchsia {
background-color: color-mix(in srgb, fuchsia 50%, transparent 50%);
}
.pink {
background-color: color-mix(in srgb, pink 50%, transparent 50%);
}
.hotpink {
background-color: color-mix(in srgb, hotpink 50%, transparent 50%);
}
.cyan {
background-color: color-mix(in srgb, cyan 50%, transparent 50%);
}
<div
style="
width: 300px;
height: 300px;
display: flex;
background-color: color-mix(in srgb, red 50%, transparent 50%);
"
>
<div
style="
width: 44px;
height: 44px;
background-color: color-mix(in srgb, yellow 50%, transparent 50%);
"
></div>
<div
style="
width: 44px;
height: 44px;
background-color: color-mix(in srgb, blue 50%, transparent 50%);
"
></div>
</div>
<script lang="ts">
import { Flex, Box } from '@threlte/flex'
import Plane from '../../Plane.svelte'
</script>
<Plane
width={300}
height={300}
color="red"
/>
<Flex
width={300}
height={300}
>
<Box>
<Plane
color="yellow"
width={44}
height={44}
depth={1}
/>
</Box>
<Box>
<Plane
color="blue"
width={44}
height={44}
depth={1}
/>
</Box>
</Flex>
<div
style="
width: 300px;
height: 300px;
display: flex;
background-color: color-mix(in srgb, red 50%, transparent 50%);
"
>
<div
style="
width: 44px;
height: 44px;
background-color: color-mix(in srgb, yellow 50%, transparent 50%);
"
></div>
<div
style="
width: 44px;
height: 44px;
flex: 1;
background-color: color-mix(in srgb, blue 50%, transparent 50%);
"
></div>
</div>
<script lang="ts">
import { Flex, Box } from '@threlte/flex'
import Plane from '../../Plane.svelte'
</script>
<Plane
width={300}
height={300}
color="red"
/>
<Flex
width={300}
height={300}
>
<Box>
<Plane
color="yellow"
width={44}
height={44}
depth={1}
/>
</Box>
<Box flex={1}>
{#snippet children({ width })}
<Plane
color="blue"
{width}
height={44}
depth={1}
/>
{/snippet}
</Box>
</Flex>
<div
style="
width: 300px;
height: 300px;
display: flex;
justify-content: flex-end;
background-color: color-mix(in srgb, red 50%, transparent 50%);
"
>
<div
style="
width: 44px;
height: 44px;
background-color: color-mix(in srgb, yellow 50%, transparent 50%);
"
></div>
<div
style="
width: 44px;
height: 44px;
background-color: color-mix(in srgb, blue 50%, transparent 50%);
"
></div>
</div>
<script lang="ts">
import { Flex, Box } from '@threlte/flex'
import Plane from '../../Plane.svelte'
</script>
<Plane
width={300}
height={300}
color="red"
/>
<Flex
width={300}
height={300}
justifyContent="FlexEnd"
>
<Box>
<Plane
color="yellow"
width={44}
height={44}
depth={1}
/>
</Box>
<Box>
<Plane
color="blue"
width={44}
height={44}
depth={1}
/>
</Box>
</Flex>
<div
style="
width: 300px;
height: 300px;
display: flex;
justify-content: flex-end;
align-items: flex-end;
background-color: color-mix(in srgb, red 50%, transparent 50%);
"
>
<div
style="
width: 44px;
height: 44px;
background-color: color-mix(in srgb, yellow 50%, transparent 50%);
"
></div>
<div
style="
width: 44px;
height: 44px;
background-color: color-mix(in srgb, blue 50%, transparent 50%);
"
></div>
</div>
<script lang="ts">
import { Flex, Box } from '@threlte/flex'
import Plane from '../../Plane.svelte'
</script>
<Plane
width={300}
height={300}
color="red"
/>
<Flex
width={300}
height={300}
justifyContent="FlexEnd"
alignItems="FlexEnd"
>
<Box>
<Plane
color="yellow"
width={44}
height={44}
depth={1}
/>
</Box>
<Box>
<Plane
color="blue"
width={44}
height={44}
depth={1}
/>
</Box>
</Flex>
<div
style="
width: 300px;
height: 300px;
display: flex;
justify-content: center;
align-items: center;
gap: 20px;
background-color: color-mix(in srgb, red 50%, transparent 50%);
"
>
<div
style="
width: 44px;
height: 44px;
background-color: color-mix(in srgb, yellow 50%, transparent 50%);
"
></div>
<div
style="
width: 44px;
height: 44px;
background-color: color-mix(in srgb, blue 50%, transparent 50%);
"
></div>
</div>
<script lang="ts">
import { Flex, Box } from '@threlte/flex'
import Plane from '../../Plane.svelte'
</script>
<Plane
width={300}
height={300}
color="red"
/>
<Flex
width={300}
height={300}
justifyContent="Center"
alignItems="Center"
gap={20}
>
<Box>
<Plane
color="yellow"
width={44}
height={44}
depth={1}
/>
</Box>
<Box>
<Plane
color="blue"
width={44}
height={44}
depth={1}
/>
</Box>
</Flex>
<div
style="
width: 300px;
height: 300px;
display: flex;
justify-content: center;
align-items: stretch;
gap: 20px;
background-color: color-mix(in srgb, red 50%, transparent 50%);
"
>
<div
style="
width: 44px;
background-color: color-mix(in srgb, yellow 50%, transparent 50%);
"
></div>
<div
style="
width: 44px;
background-color: color-mix(in srgb, blue 50%, transparent 50%);
"
></div>
</div>
<script lang="ts">
import { Flex, Box } from '@threlte/flex'
import Plane from '../../Plane.svelte'
</script>
<Plane
width={300}
height={300}
color="red"
/>
<Flex
width={300}
height={300}
justifyContent="Center"
alignItems="Stretch"
gap={20}
>
<Box
width={44}
height="auto"
>
{#snippet children({ width, height })}
<Plane
color="yellow"
{width}
{height}
depth={1}
/>
{/snippet}
</Box>
<Box
width={44}
height="auto"
>
{#snippet children({ width, height })}
<Plane
color="blue"
{width}
{height}
depth={1}
/>
{/snippet}
</Box>
</Flex>
<div
style="
width: 300px;
height: 300px;
display: flex;
justify-content: center;
align-items: stretch;
gap: 20px;
padding: 20px;
box-sizing: border-box;
background-color: color-mix(in srgb, red 50%, transparent 50%);
"
>
<div
style="
width: auto;
height: auto;
flex: 1;
background-color: color-mix(in srgb, yellow 50%, transparent 50%);
"
></div>
<div
style="
width: auto;
height: 200px;
flex: 0.5;
background-color: color-mix(in srgb, blue 50%, transparent 50%);
"
></div>
</div>
<script lang="ts">
import { Flex, Box } from '@threlte/flex'
import Plane from '../../Plane.svelte'
</script>
<Plane
width={300}
height={300}
color="red"
/>
<Flex
width={300}
height={300}
justifyContent="Center"
alignItems="Stretch"
gap={20}
padding={20}
>
<Box
width="auto"
height="auto"
flex={1}
>
{#snippet children({ width, height })}
<Plane
color="yellow"
{width}
{height}
depth={1}
/>
{/snippet}
</Box>
<Box
width="auto"
height={200}
flex={0.5}
>
{#snippet children({ width, height })}
<Plane
color="blue"
{width}
{height}
depth={1}
/>
{/snippet}
</Box>
</Flex>
<div
style="
width: 300px;
height: 300px;
display: flex;
gap: 10px;
padding: 10px;
box-sizing: border-box;
background-color: color-mix(in srgb, red 50%, transparent 50%);
"
>
<div
style="
width: 44px;
height: 44px;
background-color: color-mix(in srgb, yellow 50%, transparent 50%);
"
></div>
<div
style="
width: 44px;
height: 44px;
flex: 1;
background-color: color-mix(in srgb, blue 50%, transparent 50%);
"
></div>
</div>
<script lang="ts">
import { Flex, Box, tailwindParser } from '@threlte/flex'
import Plane from '../../Plane.svelte'
</script>
<Plane
width={300}
height={300}
color="red"
/>
<Flex
width={300}
height={300}
classParser={tailwindParser}
class="gap-10 p-10"
>
<Box class="h-44 w-44">
{#snippet children({ width, height })}
<Plane
{width}
{height}
color="yellow"
depth={1}
/>
{/snippet}
</Box>
<Box class="flex-1">
{#snippet children({ width })}
<Plane
color="blue"
{width}
height={44}
depth={1}
/>
{/snippet}
</Box>
</Flex>
<div
style="
width: 300px;
height: 300px;
display: flex;
justify-content: center;
align-items: stretch;
gap: 20px;
padding: 20px;
box-sizing: border-box;
background-color: color-mix(in srgb, red 50%, transparent 50%);
"
>
<div
style="
width: auto;
height: auto;
flex: 1;
background-color: color-mix(in srgb, yellow 50%, transparent 50%);
"
></div>
<div
style="
width: auto;
height: auto;
flex: 0.5;
display: flex;
padding: 20px;
align-items: stretch;
background-color: color-mix(in srgb, blue 50%, transparent 50%);
"
>
<div
style="
height: 44px;
width: auto;
flex: 1;
background-color: color-mix(in srgb, pink 50%, transparent 50%);
"
></div>
<div
style="
height: 44px;
width: auto;
flex: 1;
align-self: flex-end;
background-color: color-mix(in srgb, hotpink 50%, transparent 50%);
"
></div>
</div>
</div>
<script lang="ts">
import { Flex, Box } from '@threlte/flex'
import Plane from '../../Plane.svelte'
</script>
<Plane
width={300}
height={300}
color="red"
/>
<Flex
width={300}
height={300}
justifyContent="Center"
alignItems="Stretch"
gap={20}
padding={20}
>
<Box
width="auto"
height="auto"
flex={1}
>
{#snippet children({ width, height })}
<Plane
color="yellow"
{width}
{height}
depth={1}
/>
{/snippet}
</Box>
<Box
width="auto"
height="auto"
flex={0.5}
alignItems="Stretch"
padding={20}
>
{#snippet children({ width, height })}
<Plane
color="blue"
{width}
{height}
depth={1}
/>
<Box
flex={1}
width="auto"
height={44}
>
{#snippet children({ width, height })}
<Plane
color="pink"
{width}
{height}
depth={2}
/>
{/snippet}
</Box>
<Box
flex={1}
width="auto"
height={44}
alignSelf="FlexEnd"
>
{#snippet children({ width, height })}
<Plane
color="hotpink"
{width}
{height}
depth={2}
/>
{/snippet}
</Box>
{/snippet}
</Box>
</Flex>
<div
style="
width: 300px;
height: 300px;
display: flex;
justify-content: center;
align-items: stretch;
gap: 20px;
padding: 20px;
box-sizing: border-box;
background-color: color-mix(in srgb, red 50%, transparent 50%);
"
>
<div
style="
width: auto;
height: auto;
flex: 1;
display: flex;
flex-direction: column;
padding: 20px;
gap: 20px;
background-color: color-mix(in srgb, yellow 50%, transparent 50%);
"
>
<div
style="
height: auto;
width: auto;
flex: 1;
background-color: color-mix(in srgb, fuchsia 50%, transparent 50%);
"
></div>
<div
style="
height: auto;
width: auto;
flex: 1;
display: flex;
justify-content: center;
align-items: center;
background-color: color-mix(in srgb, orange 50%, transparent 50%);
"
>
<div
style="
height: 44px;
width: 44px;
background-color: color-mix(in srgb, red 50%, transparent 50%);
"
></div>
</div>
</div>
<div
style="
width: auto;
height: auto;
flex: 0.5;
display: flex;
padding: 20px;
align-items: stretch;
background-color: color-mix(in srgb, blue 50%, transparent 50%);
"
>
<div
style="
height: 44px;
width: auto;
flex: 1;
background-color: color-mix(in srgb, pink 50%, transparent 50%);
"
></div>
<div
style="
height: 44px;
width: auto;
flex: 1;
align-self: flex-end;
background-color: color-mix(in srgb, hotpink 50%, transparent 50%);
"
></div>
</div>
</div>
<script lang="ts">
import { Flex, Box } from '@threlte/flex'
import Plane from '../../Plane.svelte'
</script>
<Plane
width={300}
height={300}
color="red"
/>
<Flex
width={300}
height={300}
justifyContent="Center"
alignItems="Stretch"
gap={20}
padding={20}
>
<Box
width="auto"
height="auto"
flex={1}
gap={20}
padding={20}
flexDirection="Column"
>
{#snippet children({ width, height })}
<Plane
color="yellow"
{width}
{height}
depth={1}
/>
<Box
flex={1}
width="auto"
height="auto"
>
{#snippet children({ width, height })}
<Plane
color="fuchsia"
{width}
{height}
depth={2}
/>
{/snippet}
</Box>
<Box
flex={1}
width="auto"
height="auto"
justifyContent="Center"
alignItems="Center"
>
{#snippet children({ width, height })}
<Plane
color="orange"
{width}
{height}
depth={2}
/>
<Box
width={44}
height={44}
>
{#snippet children({ width, height })}
<Plane
color="red"
{width}
{height}
depth={3}
/>
{/snippet}
</Box>
{/snippet}
</Box>
{/snippet}
</Box>
<Box
width="auto"
height="auto"
flex={0.5}
alignItems="Stretch"
padding={20}
>
{#snippet children({ width, height })}
<Plane
color="blue"
{width}
{height}
depth={1}
/>
<Box
flex={1}
width="auto"
height={44}
>
{#snippet children({ width, height })}
<Plane
color="pink"
{width}
{height}
depth={2}
/>
{/snippet}
</Box>
<Box
flex={1}
width="auto"
height={44}
alignSelf="FlexEnd"
>
{#snippet children({ width, height })}
<Plane
color="hotpink"
{width}
{height}
depth={2}
/>
{/snippet}
</Box>
{/snippet}
</Box>
</Flex>
<script lang="ts">
import { T } from '@threlte/core'
import { OrbitControls } from '@threlte/extras'
</script>
<T.OrthographicCamera
makeDefault
near={44}
far={4400}
position={[0, 0, 3000]}
oncreate={(ref) => ref.lookAt(0, 0, 0)}
>
<OrbitControls zoomToCursor />
</T.OrthographicCamera>
<T.DirectionalLight />