From d4bd9fb9b7eb1a4a23c21212d330697e172902cf Mon Sep 17 00:00:00 2001 From: Bela Bohlender Date: Thu, 11 Apr 2024 20:39:18 +0200 Subject: [PATCH] docs: explain ref.current.setStyle, depthTest, renderOrder, vanilla tutorial, distanceToCamera --- docs/advanced/performance.md | 30 ++++- docs/advanced/pitfalls.md | 2 +- .../components-and-properties.md | 27 ++-- docs/getting-started/vanilla.md | 122 ++++++++++++++++++ docs/kits/apfel.md | 2 +- docs/kits/default.md | 2 +- docs/kits/theming.md | 2 +- docs/migration/from-html-css.md | 2 +- docs/migration/from-koestlich.md | 2 +- docs/migration/from-tailwind.md | 2 +- docs/tutorials/custom-fonts.md | 2 +- docs/tutorials/custom-materials.md | 2 +- docs/tutorials/interactivity.md | 2 +- docs/tutorials/responsive.md | 2 +- docs/tutorials/scroll.md | 2 +- docs/tutorials/sizing.md | 2 +- packages/react/src/fullscreen.tsx | 4 +- packages/uikit/src/components/fullscreen.ts | 5 +- packages/uikit/src/vanilla/fullscreen.ts | 10 +- 19 files changed, 193 insertions(+), 31 deletions(-) create mode 100644 docs/getting-started/vanilla.md diff --git a/docs/advanced/performance.md b/docs/advanced/performance.md index f6afcf21..194e41de 100644 --- a/docs/advanced/performance.md +++ b/docs/advanced/performance.md @@ -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 +} +``` + +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' diff --git a/docs/advanced/pitfalls.md b/docs/advanced/pitfalls.md index 72bab84a..85a6ce60 100644 --- a/docs/advanced/pitfalls.md +++ b/docs/advanced/pitfalls.md @@ -1,7 +1,7 @@ --- title: Pitfalls description: Pitfalls to avoid when building userinterfaces with uikit -nav: 13 +nav: 14 --- ## Asynchronous Objects inside `Content` diff --git a/docs/getting-started/components-and-properties.md b/docs/getting-started/components-and-properties.md index 087895e4..3dabc9dc 100644 --- a/docs/getting-started/components-and-properties.md +++ b/docs/getting-started/components-and-properties.md @@ -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 @@ -141,18 +141,20 @@ Every layout needs to start with a `Root` component. The `Root` component has al
View all properties specific to the `Root` component -| 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 |
## 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 @@ -164,9 +166,12 @@ The `Fullscreen` component wraps the `Root` component and binds its content dire
View all properties specific to the `Fullscreen` component -| Property | Type | -| ------------ | ------- | -| attachCamera | boolean | +| Property | Type | +| ---------------- | ------- | +| attachCamera | boolean | +| distanceToCamera | number | +| renderOrder | number | +| depthTest | boolean |
diff --git a/docs/getting-started/vanilla.md b/docs/getting-started/vanilla.md new file mode 100644 index 00000000..7e20c944 --- /dev/null +++ b/docs/getting-started/vanilla.md @@ -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 + + + + + + Document + + + +
+ + +``` + +The result should look like this +![Screenshot of resulting project](./first-layout.png) diff --git a/docs/kits/apfel.md b/docs/kits/apfel.md index f01e869f..a9f09268 100644 --- a/docs/kits/apfel.md +++ b/docs/kits/apfel.md @@ -1,7 +1,7 @@ --- title: Apfel description: All the Apfel components. -nav: 4 +nav: 5 --- ## Button diff --git a/docs/kits/default.md b/docs/kits/default.md index e16cec1d..3730b935 100644 --- a/docs/kits/default.md +++ b/docs/kits/default.md @@ -1,7 +1,7 @@ --- title: Default description: All the Default components. -nav: 5 +nav: 6 --- ## Accordion diff --git a/docs/kits/theming.md b/docs/kits/theming.md index b096b1c0..8d6fd147 100644 --- a/docs/kits/theming.md +++ b/docs/kits/theming.md @@ -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. diff --git a/docs/migration/from-html-css.md b/docs/migration/from-html-css.md index 8384ca76..20c0bec0 100644 --- a/docs/migration/from-html-css.md +++ b/docs/migration/from-html-css.md @@ -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. diff --git a/docs/migration/from-koestlich.md b/docs/migration/from-koestlich.md index 5adf7471..6bdb77c1 100644 --- a/docs/migration/from-koestlich.md +++ b/docs/migration/from-koestlich.md @@ -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: diff --git a/docs/migration/from-tailwind.md b/docs/migration/from-tailwind.md index ef5b10af..487ba35d 100644 --- a/docs/migration/from-tailwind.md +++ b/docs/migration/from-tailwind.md @@ -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**. diff --git a/docs/tutorials/custom-fonts.md b/docs/tutorials/custom-fonts.md index 12219883..905540ea 100644 --- a/docs/tutorials/custom-fonts.md +++ b/docs/tutorials/custom-fonts.md @@ -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). diff --git a/docs/tutorials/custom-materials.md b/docs/tutorials/custom-materials.md index d04675db..ac133172 100644 --- a/docs/tutorials/custom-materials.md +++ b/docs/tutorials/custom-materials.md @@ -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`. diff --git a/docs/tutorials/interactivity.md b/docs/tutorials/interactivity.md index 94556b93..e0737bc7 100644 --- a/docs/tutorials/interactivity.md +++ b/docs/tutorials/interactivity.md @@ -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. diff --git a/docs/tutorials/responsive.md b/docs/tutorials/responsive.md index a50431ff..153df23f 100644 --- a/docs/tutorials/responsive.md +++ b/docs/tutorials/responsive.md @@ -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 diff --git a/docs/tutorials/scroll.md b/docs/tutorials/scroll.md index 63054577..decb5fcf 100644 --- a/docs/tutorials/scroll.md +++ b/docs/tutorials/scroll.md @@ -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. diff --git a/docs/tutorials/sizing.md b/docs/tutorials/sizing.md index 4c518675..a8787a66 100644 --- a/docs/tutorials/sizing.md +++ b/docs/tutorials/sizing.md @@ -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. diff --git a/packages/react/src/fullscreen.tsx b/packages/react/src/fullscreen.tsx index eb8522ef..c16f649f 100644 --- a/packages/react/src/fullscreen.tsx +++ b/packages/react/src/fullscreen.tsx @@ -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, 'sizeX' | 'sizeY' | 'pixelSize'> & { + props: FullscreenProperties & { children?: ReactNode attachCamera?: boolean distanceToCamera?: number diff --git a/packages/uikit/src/components/fullscreen.ts b/packages/uikit/src/components/fullscreen.ts index 3b2c1dae..ed9a0498 100644 --- a/packages/uikit/src/components/fullscreen.ts +++ b/packages/uikit/src/components/fullscreen.ts @@ -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 export function updateSizeFullscreen( sizeX: Signal, diff --git a/packages/uikit/src/vanilla/fullscreen.ts b/packages/uikit/src/vanilla/fullscreen.ts index fd3f2e1c..613bcbb5 100644 --- a/packages/uikit/src/vanilla/fullscreen.ts +++ b/packages/uikit/src/vanilla/fullscreen.ts @@ -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() @@ -15,7 +21,7 @@ export class Fullscreen extends Root { constructor( private renderer: WebGLRenderer, private distanceToCamera?: number, - properties?: Omit, + properties?: FullscreenProperties, defaultProperties?: AllOptionalProperties, fontFamilies?: FontFamilies, ) {