@threlte/flex
Getting Started
Placing content and making layouts in 3D is hard. The flexbox engine
Yoga
is a cross-platform layout engine which
implements the flexbox spec. The package @threlte/flex
provides components to
easily use Yoga
in Threlte.
<script lang="ts">
import { useTask, useThrelte } from '@threlte/core'
import { interactivity, transitions } from '@threlte/extras'
import { Box } from '@threlte/flex'
import { tick } from 'svelte'
import Button from './Button.svelte'
import Label from './Label.svelte'
import Matcap from './Matcap.svelte'
import Window from './Window.svelte'
export let windowWidth: number
export let windowHeight: number
export let rows = 5
export let columns = 5
export let size: any
let page = 1
$: offset = (page - 1) * rows * columns
interactivity()
transitions()
const { renderStage, autoRender, renderer, scene, camera } = useThrelte()
autoRender.set(false)
useTask(
async () => {
await tick()
renderer.render(scene, camera.current)
},
{ stage: renderStage, autoInvalidate: false }
)
</script>
<Window
title="Matcaps"
width={windowWidth}
height={windowHeight}
>
<Box class="h-full w-full flex-col items-stretch gap-10 p-10">
{#each new Array(rows) as _, rowIndex}
<Box class="h-auto w-full flex-1 items-center justify-evenly gap-10">
{#each new Array(columns) as _, columnIndex}
{@const index = rowIndex * columns + columnIndex}
<Box
class="h-full w-full flex-1"
let:width
let:height
>
<Matcap
{width}
{height}
matcapIndex={offset + index}
gridIndex={index}
format={size}
/>
</Box>
{/each}
</Box>
{/each}
<Box
order={999}
class="h-40 w-auto items-center justify-center gap-10"
>
<Button
class="h-full w-auto flex-1"
z={15}
text="← PREVIOUS PAGE"
order={0}
on:click={() => {
page = Math.max(1, page - 1)
}}
/>
<Box
class="h-full w-auto flex-1"
order={1}
>
<Label
z={10.1}
fontSize="xl"
text={`PAGE: ${page}`}
/>
</Box>
<Button
class="h-full w-auto flex-1"
z={15}
text="NEXT PAGE →"
order={2}
on:click={() => {
page = Math.min(10, page + 1)
}}
/>
</Box>
</Box>
</Window>
Installation
npm install @threlte/flex
Usage
Basic Example
Use the component <Flex>
to create a flexbox
container. Since there’s no viewport to fill, you must specify the size of the
container. Add flex items with the component <Box>
.
<script lang="ts">
import { Flex } from '@threlte/flex'
import Plane from './Plane.svelte'
</script>
<Flex
width={100}
height={100}
>
<Box>
<Plane
width={20}
height={20}
/>
</Box>
<Box>
<Plane
width={20}
height={20}
/>
</Box>
</Flex>
Flex Props
The components <Flex>
and <Box>
accept props to configure the flexbox. If no
width or height is specified on <Box>
components, a bounding box is used to
determine the size of the flex item. The computed width or height may be
different from what is specified on the <Box>
component, depending on the
flexbox configuration. To make use of the calculated dimensions of a flex item, use the slot props width
and height
.
<Flex
width={100}
height={100}
flexDirection="Column"
justifyContent="SpaceEvenly"
alignItems="Stretch"
>
<Box
width="auto"
height="auto"
flex={1}
let:width
let:height
>
<Plane
{width}
{height}
/>
</Box>
<Box
width="auto"
height="auto"
flex={1}
let:width
let:height
>
<Plane
{width}
{height}
/>
</Box>
</Flex>
Nested Flex
Every <Box>
component is also a flex container. Nesting <Box>
components
allows you to create complex layouts.
<Flex
width={100}
height={100}
flexDirection="Column"
justifyContent="SpaceEvenly"
alignItems="Stretch"
>
<Box
width="auto"
height="auto"
flex={1}
justifyContent="SpaceEvenly"
alignItems="Stretch"
padding={20}
margin={20}
gap={20}
let:width
let:height
>
<Plane
color="orange"
{width}
{height}
depth={1}
/>
<Box
height="auto"
flex={1}
let:width
let:height
>
<Plane
color="blue"
{width}
{height}
depth={2}
/>
</Box>
<Box
height="auto"
flex={1}
let:width
let:height
>
<Plane
color="red"
{width}
{height}
depth={2}
/>
</Box>
</Box>
<Box
height="auto"
width="auto"
flex={1}
let:width
let:height
>
<Plane
depth={1}
{width}
{height}
/>
</Box>
</Flex>
Align Flex Container
The component <Align>
can be used to align the resulting flex container.
<script lang="ts">
import { Align } from '@threlte/extras'
import { Flex } from '@threlte/flex'
import Plane from './Plane.svelte'
</script>
<Align
y={1}
let:align
>
<Flex
width={100}
height={100}
on:reflow={align}
>
<Box>
<Plane
width={20}
height={20}
/>
</Box>
<Box>
<Plane
width={20}
height={20}
/>
</Box>
</Flex>
</Align>
class
Using the Prop The prop class
can be used on <Box>
and <Flex>
to easily configure the
flexbox with predefined class names just as you would do in CSS. In order to use
the prop, you need to create a ClassParser
using the utility
createClassParser
which accepts a
single string and returns NodeProps
. Let’s assume, you want to create a parser
that supports the following class names:
.container {
display: flex;
flex-direction: row;
justify-content: center;
align-items: stretch;
gap: 10px;
padding: 10px;
}
.item {
width: auto;
height: auto;
flex: 1;
}
You then need to create a ClassParser
which returns the corresponding props:
import { createClassParser } from '@threlte/flex'
const classParser = createClassParser((string, props) => {
const classNames = string.split(' ')
for (const className of classNames) {
switch (className) {
case 'container':
props.flexDirection = 'Row'
props.justifyContent = 'Center'
props.alignItems = 'Stretch'
props.gap = 10
props.padding = 10
break
case 'item':
props.width = 'auto'
props.height = 'auto'
props.flex = 1
}
}
return props
})
Now you can use the prop class
on <Flex>
and <Box>
to configure the flexbox:
<Flex
width={100}
height={100}
{classParser}
class="container"
>
<Box class="item">
<Plane
width={20}
height={20}
/>
</Box>
<Box class="item">
<Plane
width={20}
height={20}
/>
</Box>
</Flex>
@threlte/flex
ships with a default ClassParser
which supports Tailwind-like
class names.