Skip to main content

Using with React Three Fiber

In this guide, you’ll add a button and analog control to a @react-three/fiber scene, binding their values to a zustand store to manage and react to touch inputs.

This tutorial will assume some React and Typescript knowledge, and will be based on this starter codesandbox, so just fork it and follow along!

What we'll build

The result is a cube that moves and scales interactively based on touch input, providing a basic but functional touch-driven 3D application.

Prerequisites

Make sure you’ve installed the following packages:

npm install @react-three/fiber zustand xyba-elements

Set Up a zustand Store

First, we’ll set up a zustand store to manage state for the button and analog input.

// src/store.ts
import { create } from "zustand";

export type ControlsStore = {
isScaled: boolean;
analogValue: number[];
setIsScaled: (pressed: boolean) => void;
setAnalogValue: (value: number[]) => void;
};

export const useControlsStore = create<ControlsStore>((set) => ({
isScaled: false,
analogValue: [0, 0],
setIsScaled: (value: boolean) => set({ isScaled: value }),
setAnalogValue: (value: number[]) => set({ analogValue: value }),
}));

Add the Virtual Controller

Now, create a component to add an analog and button from xyba-elements and link them to the zustand store.

// src/VirtualController.tsx
import React from "react";
import { Analog, Button } from "xyba-elements";
import type { AnalogElement } from "xyba-elements";
import { useControlsStore } from "./store";

export default function VirtualController() {
const setIsScaled = useControlsStore((state) => state.setIsScaled);
const setAnalogValue = useControlsStore((state) => state.setAnalogValue);

return (
<div className="virtual-controller">
<Analog
className="analog"
onChange={(event) =>
setAnalogValue((event.target as AnalogElement).value)
}
/>
<Button
className="scale-button"
onPress={() => setIsScaled(true)}
onRelease={() => setIsScaled(false)}
>
S
</Button>
</div>
);
}

Render the 3D Scene with Controls

With the store and controls ready, let’s integrate them in a react-three-fiber scene.

// src/App.tsx
import React from "react";
import { Canvas, Vector3 } from "@react-three/fiber";
import { useControlsStore } from "./store";
import VirtualController from "./VirtualController";
import "./styles.css";

function Box({ position, scaled }: { position: Vector3; scaled: boolean }) {
return (
<mesh position={position} scale={scaled ? [2, 2, 2] : [1, 1, 1]}>
<boxGeometry args={[1, 1, 1]} />
<meshStandardMaterial />
</mesh>
);
}

export default function App() {
const analogValue = useControlsStore((state) => state.analogValue);
const isScaled = useControlsStore((state) => state.isScaled);

return (
<div className="App">
<VirtualController />
<Canvas>
<ambientLight args={[0xff0000]} intensity={0.1} />
<directionalLight position={[0, 0, 5]} intensity={0.5} />
<Box
position={[analogValue[0], -analogValue[1], 0]}
scaled={isScaled}
/>
</Canvas>
</div>
);
}

Style the Virtual Controller

Keep the controls visible and position them for easy access by adding a few basic styles.

/* src/styles.css */
.virtual-controller {
position: fixed;
top: 0;
left: 0;
height: 100%;
width: 100%;
z-index: 10;
}

.virtual-controller .analog {
position: absolute;
bottom: 60px;
left: 40px;
}

.virtual-controller .scale-button {
position: absolute;
bottom: 100px;
right: 40px;
}

Now, you should see a simple cube that responds to your touch controls!