@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'
const browser = typeof window !== 'undefined'
type Example = {
name: string
dom: ConstructorOfATypedSvelteComponent
threlte: ConstructorOfATypedSvelteComponent
}
let selected: string = browser ? sessionStorage.selected || '' : ''
let components: Example[] = []
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)
$: example = components.find((e) => e.name === selected)
$: if (browser && selected.length) sessionStorage.selected = selected
</script>
<svelte:window
on:keydown={(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>
<svelte:component this={example.dom} />
</div>
</div>
<div class="threlte">
<Canvas toneMapping={NoToneMapping}>
<Common />
<svelte:component this={example.threlte} />
</Canvas>
</div>
</div>
{/if}
<nav>
<select bind:value={selected}>
{#each components as { 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 { DEG2RAD } from 'three/src/math/MathUtils.js'
export let size = 1
export let thickness = 0.02
export let arrows = false
export let hideX = false
export let hideY = false
export let hideZ = false
$: showAny = !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]}
on:create={({ ref }) => {
ref.rotateZ(90 * DEG2RAD)
}}
/>
<T.MeshBasicMaterial color="red" />
{#if arrows}
<T.Mesh position.x={size / 2 + thickness * 3}>
<T.ConeGeometry
args={[thickness * 3, thickness * 6]}
on:create={({ ref }) => {
ref.rotateZ(-90 * 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]}
on:create={({ ref }) => {
ref.rotateX(90 * DEG2RAD)
}}
/>
<T.MeshBasicMaterial color="blue" />
{#if arrows}
<T.Mesh position.z={size / 2 + thickness * 3}>
<T.ConeGeometry
args={[thickness * 3, thickness * 6]}
on:create={({ ref }) => {
ref.rotateX(90 * DEG2RAD)
}}
/>
<T.MeshBasicMaterial color="blue" />
</T.Mesh>
{/if}
</T.Mesh>
{/if}
<script lang="ts">
import { T } from '@threlte/core'
export let color: string = 'white'
export let height = 1
export let width = 1
export let depth = 0
</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
class="red"
style="width: 300px; height: 300px; display: flex;"
>
<div
class="yellow"
style="width: 44px; height: 44px;"
/>
<div
class="blue"
style="width: 44px; height: 44px;"
/>
</div>
<style lang="postcss">
@import '../../colors.postcss';
</style>
<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
class="red"
style="width: 300px; height: 300px; display: flex;"
>
<div
class="yellow"
style="width: 44px; height: 44px;"
/>
<div
class="blue"
style="width: 44px; height: 44px; flex: 1"
/>
</div>
<style lang="postcss">
@import '../../colors.postcss';
</style>
<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}
let:width
>
<Plane
color="blue"
{width}
height={44}
depth={1}
/>
</Box>
</Flex>
<div
class="red"
style="width: 300px; height: 300px; display: flex; justify-content: flex-end;"
>
<div
class="yellow"
style="width: 44px; height: 44px;"
/>
<div
class="blue"
style="width: 44px; height: 44px;"
/>
</div>
<style lang="postcss">
@import '../../colors.postcss';
</style>
<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
class="red"
style="width: 300px; height: 300px; display: flex; justify-content: flex-end; align-items: flex-end;"
>
<div
class="yellow"
style="width: 44px; height: 44px;"
/>
<div
class="blue"
style="width: 44px; height: 44px;"
/>
</div>
<style lang="postcss">
@import '../../colors.postcss';
</style>
<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
class="red"
style="width: 300px; height: 300px; display: flex; justify-content: center; align-items: center; gap: 20px"
>
<div
class="yellow"
style="width: 44px; height: 44px;"
/>
<div
class="blue"
style="width: 44px; height: 44px;"
/>
</div>
<style lang="postcss">
@import '../../colors.postcss';
</style>
<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
class="red"
style="width: 300px; height: 300px; display: flex; justify-content: center; align-items: stretch; gap: 20px"
>
<div
class="yellow"
style="width: 44px;"
/>
<div
class="blue"
style="width: 44px;"
/>
</div>
<style lang="postcss">
@import '../../colors.postcss';
</style>
<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"
let:width
let:height
>
<Plane
color="yellow"
{width}
{height}
depth={1}
/>
</Box>
<Box
width={44}
height="auto"
let:width
let:height
>
<Plane
color="blue"
{width}
{height}
depth={1}
/>
</Box>
</Flex>
<div
class="red"
style="width: 300px; height: 300px; display: flex; justify-content: center; align-items: stretch; gap: 20px; padding: 20px; box-sizing: border-box;"
>
<div
class="yellow"
style="width: auto; height: auto; flex: 1;"
/>
<div
class="blue"
style="width: auto; height: 200px; flex: 0.5"
/>
</div>
<style lang="postcss">
@import '../../colors.postcss';
</style>
<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}
let:width
let:height
>
<Plane
color="yellow"
{width}
{height}
depth={1}
/>
</Box>
<Box
width="auto"
height={200}
flex={0.5}
let:width
let:height
>
<Plane
color="blue"
{width}
{height}
depth={1}
/>
</Box>
</Flex>
<div
class="red"
style="width: 300px; height: 300px; display: flex; gap: 10px; padding: 10px; box-sizing: border-box;"
>
<div
class="yellow"
style="width: 44px; height: 44px;"
/>
<div
class="blue"
style="width: 44px; height: 44px; flex: 1"
/>
</div>
<style lang="postcss">
@import '../../colors.postcss';
</style>
<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"
let:width
let:height
>
<Plane
{width}
{height}
color="yellow"
depth={1}
/>
</Box>
<Box
class="flex-1"
let:width
>
<Plane
color="blue"
{width}
height={44}
depth={1}
/>
</Box>
</Flex>
<div
class="red"
style="width: 300px; height: 300px; display: flex; justify-content: center; align-items: stretch; gap: 20px; padding: 20px; box-sizing: border-box;"
>
<div
class="yellow"
style="width: auto; height: auto; flex: 1;"
/>
<div
class="blue"
style="width: auto; height: auto; flex: 0.5; display: flex; padding: 20px; align-items: stretch;"
>
<div
class="pink"
style="height: 44px; width: auto; flex: 1;"
/>
<div
class="hotpink"
style="height: 44px; width: auto; flex: 1; align-self: flex-end;"
/>
</div>
</div>
<style lang="postcss">
@import '../../colors.postcss';
</style>
<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}
let:width
let:height
>
<Plane
color="yellow"
{width}
{height}
depth={1}
/>
</Box>
<Box
width="auto"
height="auto"
flex={0.5}
alignItems="Stretch"
padding={20}
let:width
let:height
>
<Plane
color="blue"
{width}
{height}
depth={1}
/>
<Box
flex={1}
width="auto"
height={44}
let:width
let:height
>
<Plane
color="pink"
{width}
{height}
depth={2}
/>
</Box>
<Box
flex={1}
width="auto"
height={44}
alignSelf="FlexEnd"
let:width
let:height
>
<Plane
color="hotpink"
{width}
{height}
depth={2}
/>
</Box>
</Box>
</Flex>
<div
class="red"
style="width: 300px; height: 300px; display: flex; justify-content: center; align-items: stretch; gap: 20px; padding: 20px; box-sizing: border-box;"
>
<div
class="yellow"
style="width: auto; height: auto; flex: 1; display: flex; flex-direction: column; padding: 20px; gap: 20px"
>
<div
class="fuchsia"
style="height: auto; width: auto; flex: 1;"
/>
<div
class="orange"
style="height: auto; width: auto; flex: 1; display: flex; justify-content: center; align-items: center;"
>
<div
class="red"
style="height: 44px; width: 44px;"
/>
</div>
</div>
<div
class="blue"
style="width: auto; height: auto; flex: 0.5; display: flex; padding: 20px; align-items: stretch;"
>
<div
class="pink"
style="height: 44px; width: auto; flex: 1;"
/>
<div
class="hotpink"
style="height: 44px; width: auto; flex: 1; align-self: flex-end;"
/>
</div>
</div>
<style lang="postcss">
@import '../../colors.postcss';
</style>
<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"
let:width
let:height
>
<Plane
color="yellow"
{width}
{height}
depth={1}
/>
<Box
flex={1}
width="auto"
height="auto"
let:width
let:height
>
<Plane
color="fuchsia"
{width}
{height}
depth={2}
/>
</Box>
<Box
flex={1}
width="auto"
height="auto"
justifyContent="Center"
alignItems="Center"
let:width
let:height
>
<Plane
color="orange"
{width}
{height}
depth={2}
/>
<Box
width={44}
height={44}
let:width
let:height
>
<Plane
color="red"
{width}
{height}
depth={3}
/>
</Box>
</Box>
</Box>
<Box
width="auto"
height="auto"
flex={0.5}
alignItems="Stretch"
padding={20}
let:width
let:height
>
<Plane
color="blue"
{width}
{height}
depth={1}
/>
<Box
flex={1}
width="auto"
height={44}
let:width
let:height
>
<Plane
color="pink"
{width}
{height}
depth={2}
/>
</Box>
<Box
flex={1}
width="auto"
height={44}
alignSelf="FlexEnd"
let:width
let:height
>
<Plane
color="hotpink"
{width}
{height}
depth={2}
/>
</Box>
</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]}
on:create={({ ref }) => ref.lookAt(0, 0, 0)}
>
<OrbitControls zoomToCursor />
</T.OrthographicCamera>
<T.DirectionalLight />