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!