Skip to content

Commit

Permalink
docs: explain ref.current.setStyle, depthTest, renderOrder, vanilla t…
Browse files Browse the repository at this point in the history
…utorial, distanceToCamera
  • Loading branch information
bbohlender committed Apr 11, 2024
1 parent 14d404e commit d4bd9fb
Show file tree
Hide file tree
Showing 19 changed files with 193 additions and 31 deletions.
30 changes: 28 additions & 2 deletions docs/advanced/performance.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,38 @@
---
title: Performance
description: Important considerations for building performant user interfaces with uikit.
nav: 14
nav: 15
---

## Avoid React Re-renders

When frequently changing properties of uikit components and especially when animating properties on every frame. We recommend modifying properties using a signal. This approach is similar to react-spring and allows to modify the properties of a uikit component without any property diffing. The following code shows how to animate the background opacity on every frame without interfering with react.
When frequently changing properties of uikit components and especially when animating properties on every frame. We recommend modifying properties using `ref.current.setStyle` or using a signal.

### using `ref.current.setStyle`

This approach is similar to html/css. The following code shows how to animate the background opacity on every frame without interfering with react.

```jsx
import { Container } from '@react-three/uikit'
import { useMemo } from 'react'
import { useFrame } from '@react-three/fiber'
import { signal } from '@preact/signals-core'

export function AnimateBackground() {
const ref = useRef()
useFrame(({ clock }) => {
//continuously animate between 0 and 1
ref.current.setStyle({ opacity: Math.sin(clock.getElapsedTime()) / 2 + 0.5 })
})
return <Container ref={ref} backgroundOpacity={0}></Container>
}
```

Setting executing `setStyle(undefined)` resets all changes back to the initial properties provided to the directly to the component.

### using signals

This approach is similar to react-spring and allows to modify the properties of a uikit component without any property diffing. The following code shows how to animate the background opacity on every frame without interfering with react.

```jsx
import { Container } from '@react-three/uikit'
Expand Down
2 changes: 1 addition & 1 deletion docs/advanced/pitfalls.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: Pitfalls
description: Pitfalls to avoid when building userinterfaces with uikit
nav: 13
nav: 14
---

## Asynchronous Objects inside `Content`
Expand Down
27 changes: 16 additions & 11 deletions docs/getting-started/components-and-properties.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ In addition to the flexbox properties, the container has properties for styling

## Root

Every layout needs to start with a `Root` component. The `Root` component has all the properties of a `Container` component. The `pixelSize` property of the `Root` component allows you to specify the relation of pixels inside the layout with the three.js units in the scene. The `anchorX` and `anchorY` properties allow you to specify where the `Root` component is anchored in relation to its position. The `sizeX` and `sizeY` properties can be used to give the layout a fixed size in three.js units.
Every layout needs to start with a `Root` component. The `Root` component has all the properties of a `Container` component. The `pixelSize` property of the `Root` component allows you to specify the relation of pixels inside the layout with the three.js units in the scene. The `anchorX` and `anchorY` properties allow you to specify where the `Root` component is anchored in relation to its position. The `sizeX` and `sizeY` properties can be used to give the layout a fixed size in three.js units. The `Root` component also allows to control the `renderOrder` and `depthTest` of the whole user interface.

```jsx
<Root sizeX={2} sizeY={1} flexDirection="row">
Expand All @@ -141,18 +141,20 @@ Every layout needs to start with a `Root` component. The `Root` component has al
<details>
<summary>View all properties specific to the `Root` component</summary>

| Property | Type |
| --------- | ------------------------- |
| anchorX | "left", "center", "right" |
| anchorY | "top", "center", "bottom" |
| sizeX | number |
| sizeY | number |
| Property | Type |
| ----------- | ------------------------- |
| anchorX | "left", "center", "right" |
| anchorY | "top", "center", "bottom" |
| sizeX | number |
| sizeY | number |
| renderOrder | number |
| depthTest | boolean |

</details>

## Fullscreen

The `Fullscreen` component wraps the `Root` component and binds its content directly to the viewport. The `Fullscreen` component automatically sets the correct pixelSize, sizeX, and sizeY properties on the `Root` component so that pixel sizes align with the pixels of the screen. In addition, the `Fullscreen` component has all the properties of the `Container` component.
The `Fullscreen` component wraps the `Root` component and binds its content directly to the viewport based on the provided `distanceToCamera`. The `Fullscreen` component automatically sets the correct pixelSize, sizeX, and sizeY properties on the `Root` component so that pixel sizes align with the pixels of the screen. In addition, the `Fullscreen` component has all the properties of the `Container` component.

```jsx
<Fullscreen flexDirection="row">
Expand All @@ -164,9 +166,12 @@ The `Fullscreen` component wraps the `Root` component and binds its content dire
<details>
<summary>View all properties specific to the `Fullscreen` component</summary>

| Property | Type |
| ------------ | ------- |
| attachCamera | boolean |
| Property | Type |
| ---------------- | ------- |
| attachCamera | boolean |
| distanceToCamera | number |
| renderOrder | number |
| depthTest | boolean |

</details>

Expand Down
122 changes: 122 additions & 0 deletions docs/getting-started/vanilla.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
---
title: Vanilla Three.js
description: Build your first layout with with uikit and vanilla threejs.
nav: 4
---

The vanilla version of uikit allows to build user interfaces with plain Three.js.

### Differences to @react-three/uikit

The vanilla version of uikit (`@pmndrs/uikit`) is decoupled from react. Therefore features such providing defaults via context is not available. Furthermore, no event system is available out of the box. For interactivity, such as hover effects, developers have to attach their own event system by emitting pointer events to the UI elments:

```js
uiElement.dispatchEvent({ type: 'pointerOver', target: uiElement, nativeEvent: { pointerId: 1 } })
```

Aside from interacitivty and contexts, every feature is available.

## Building a user interface with `@pmndrs/uikit`

The first step is to install the dependencies.

`npm i three @pmndrs/uikit`

Next, we create the `index.js` file and import the necessary dependencies and setup a threejs scene.

```js
import { PerspectiveCamera, Scene, WebGLRenderer } from 'three'
import { reversePainterSortStable, Container, Root } from '@pmndrs/uikit'

const camera = new PerspectiveCamera(70, 1, 0.01, 100)
camera.position.z = 10
const scene = new Scene()
const canvas = document.getElementById('root') as HTMLCanvasElement
const renderer = new WebGLRenderer({ antialias: true, canvas })
```

Now, we can start defining the actual layout. Every layout must start with a `Root` element (or an element that wraps the `Root` element, such as the `Fullscreen` component). In this example, the `Root` is of size 2 by 1 (three.js units). The `Root` has a horizontal (row) flex-direction, with 2 `Container` children, filling its width equally with a margin around them.

More in-depth information on the Flexbox properties can be found [here](https://yogalayout.dev/docs/).

```js
const root = new Root(camera, renderer, undefined, {
flexDirection: "row",
padding: 100,
gap: 100
})
scene.add(root)

const defaultProperties = {
backgroundOpacity: 0.5,
}

const container1 = new Container(
root,
{
flexGrow: 1,
hover: { backgroundOpacity: 1 }
backgroundColor: "red"
},
defaultProperties
)
root.add(container1)

const container2 = new Container(
root,
{
flexGrow: 1,
backgroundOpacity: 0.5,
hover: { backgroundOpacity: 1 },
backgroundColor: "blue"
},
defaultProperties
)
root.add(container2)
```

All properties of the user interface elements can be modified using `container.setProperties({...})`. The last step is to setup the frameloop, setup resizing, enable local clipping, and setup the transparency sort required for uikit. Notice that the root component needs to be updated every frame using `root.update(delta)`.

```js
renderer.setAnimationLoop(animation)
renderer.localClippingEnabled = true
renderer.setTransparentSort(reversePainterSortStable)

function updateSize() {
renderer.setSize(window.innerWidth, window.innerHeight)
renderer.setPixelRatio(window.devicePixelRatio)
camera.aspect = window.innerWidth / window.innerHeight
camera.updateProjectionMatrix()
}

updateSize()
window.addEventListener('resize', updateSize)

let prev: number | undefined
function animation(time: number) {
const delta = prev == null ? 0 : time - prev
prev = time
root.update(delta)
renderer.render(scene, camera)
}
```

If you use vite (`npm i vite`), you can create a `index.html` file, add the following content, and run `npx vite`.

```html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<script type="module" src="index.jsx"></script>
</head>
<body style="margin: 0;">
<div id="root" style="width: 100dvw; height: 100dvh;"></div>
</body>
</html>
```

The result should look like this
![Screenshot of resulting project](./first-layout.png)
2 changes: 1 addition & 1 deletion docs/kits/apfel.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: Apfel
description: All the Apfel components.
nav: 4
nav: 5
---

## Button
Expand Down
2 changes: 1 addition & 1 deletion docs/kits/default.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: Default
description: All the Default components.
nav: 5
nav: 6
---

## Accordion
Expand Down
2 changes: 1 addition & 1 deletion docs/kits/theming.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: Theming
description: How to customize the theme of the components from the kits.
nav: 6
nav: 7
---

When installing components from any kit, the `uikit cli` always adds a `theme.tsx` file. The values inside this file can be freely adapted to create a custom theme.
Expand Down
2 changes: 1 addition & 1 deletion docs/migration/from-html-css.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: Migration from HTML/CSS
description: How to migrate a user interface from HTML/CSS to uikit.
nav: 17
nav: 18
---

uikit is inspired by HTML/CSS. Therefore, many properties are similar with minor syntactical difference.
Expand Down
2 changes: 1 addition & 1 deletion docs/migration/from-koestlich.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: Migration from Koestlich
description: How to migrate a user interface from koestlich to uikit.
nav: 15
nav: 16
---

This guide is intended for developers migrating their user interface from Koestlich to uikit. The migration involves several changes to properties and components to better align with uikit's design principles. Here's what you need to know:
Expand Down
2 changes: 1 addition & 1 deletion docs/migration/from-tailwind.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: Migration from Tailwind
description: How to migrate a user interface from tailwind to uikit.
nav: 16
nav: 17
---

uikit is inspired by tailwind. Therefore, many properties are similar with minor syntactical difference. The major differences are **sizing** and **defaults**.
Expand Down
2 changes: 1 addition & 1 deletion docs/tutorials/custom-fonts.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: Custom Fonts
description: How to build, set up, and use custom fonts.
nav: 12
nav: 13
---

The `Text` component enables rendering text using multi-channel signed distance functions (MSDF). By default, uikit provides the Inter font. A custom font can be converted from a `.ttf` file to an MSDF representation as a JSON and a corresponding texture using [msdf-bmfont-xml](https://www.npmjs.com/package/msdf-bmfont-xml).
Expand Down
2 changes: 1 addition & 1 deletion docs/tutorials/custom-materials.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: Custom Materials
description: How to use custom materials in uikit.
nav: 11
nav: 12
---

uikit allows to provide custom material classes for the background and border on the `Text`, `Container`, and `Image` components. This allows to change the appearance and use more complex (and computationally expensive) materials such as a `MeshPhysicalMaterial`.
Expand Down
2 changes: 1 addition & 1 deletion docs/tutorials/interactivity.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: Interactivity
description: How to make the UI elements interactive.
nav: 7
nav: 8
---

Every UI component can receive the same events as [any other R3F element](https://docs.pmnd.rs/react-three-fiber/api/events). In addition to these event listeners, uikit provides properties such as `hover` and `active` for all components. These properties allow the element to overwrite other properties if it is hovered or clicked.
Expand Down
2 changes: 1 addition & 1 deletion docs/tutorials/responsive.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: Responsive
description: How to make the ui elements responsive.
nav: 8
nav: 9
---

Building responsive UIs in uikit is inspired by [responsiveness in tailwind](https://tailwindcss.com/docs/responsive-design). Using the concept of breakponts UI elements can be styled based on the size of the root element. These breakpoints are
Expand Down
2 changes: 1 addition & 1 deletion docs/tutorials/scroll.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: Scrolling
description: How to use overflow, scrolling, and clipping.
nav: 9
nav: 10
---

uikit handles clipping and scrolling out of the box by specifying `overflow="scroll"` or `overflow="hidden"` on any UI element.
Expand Down
2 changes: 1 addition & 1 deletion docs/tutorials/sizing.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: Sizing
description: How to size elements and use pixelSize, sizeX, and sizeY.
nav: 10
nav: 11
---

**TLDR**: The size of the Root element is defined in three.js units through the optional `sizeX` and `sizeY` parameters. The `pixelSize` parameter allows you to define how big one pixel in the UI is in relation to one three.js unit.
Expand Down
4 changes: 2 additions & 2 deletions packages/react/src/fullscreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ import { Root } from './root.js'
import { batch, signal } from '@preact/signals-core'
import { RootState, createPortal, useStore, useThree } from '@react-three/fiber'
import { EventHandlers } from '@react-three/fiber/dist/declarations/src/core/events.js'
import { RootProperties, WithReactive, updateSizeFullscreen } from '@pmndrs/uikit/internals'
import { FullscreenProperties, RootProperties, updateSizeFullscreen } from '@pmndrs/uikit/internals'
import { ComponentInternals } from './ref.js'

export const Fullscreen: (
props: Omit<RootProperties & WithReactive<{ pixelSize?: number }>, 'sizeX' | 'sizeY' | 'pixelSize'> & {
props: FullscreenProperties & {
children?: ReactNode
attachCamera?: boolean
distanceToCamera?: number
Expand Down
5 changes: 4 additions & 1 deletion packages/uikit/src/components/fullscreen.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { Signal } from '@preact/signals-core'
import type { Signal } from '@preact/signals-core'
import { Camera, OrthographicCamera, PerspectiveCamera } from 'three'
import type { RootProperties } from './root.js'

export type FullscreenProperties = Omit<RootProperties, 'sizeX' | 'sizeY' | 'pixelSize' | 'anchorX' | 'anchorY'>

export function updateSizeFullscreen(
sizeX: Signal<number>,
Expand Down
10 changes: 8 additions & 2 deletions packages/uikit/src/vanilla/fullscreen.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import { OrthographicCamera, PerspectiveCamera, Vector2, WebGLRenderer } from 'three'
import { Root } from './root.js'
import { FontFamilies, RootProperties, AllOptionalProperties, updateSizeFullscreen } from '../internals.js'
import {
FontFamilies,
RootProperties,
AllOptionalProperties,
updateSizeFullscreen,
FullscreenProperties,
} from '../internals.js'
import { Signal, batch, signal } from '@preact/signals-core'

const vectorHelper = new Vector2()
Expand All @@ -15,7 +21,7 @@ export class Fullscreen extends Root {
constructor(
private renderer: WebGLRenderer,
private distanceToCamera?: number,
properties?: Omit<RootProperties, 'sizeX' | 'sizeY' | 'pixelSize'>,
properties?: FullscreenProperties,
defaultProperties?: AllOptionalProperties,
fontFamilies?: FontFamilies,
) {
Expand Down

0 comments on commit d4bd9fb

Please sign in to comment.