Introduction
React-three-fiber is a React renderer for three.js.
npm install three @react-three/fiber
Why?
Build your scene declaratively with re-usable, self-contained components that react to state, are readily interactive and can tap into React's ecosystem.
Does it have limitations?
None. Everything that works in three.js will work here without exception.
Can it keep up with frequent updates to three.js?
Yes. There is no hard dependency on a particular three.js version, it does not wrap or duplicate a single three.js class. It merely expresses three.js in JSX: <mesh />
becomes new THREE.Mesh()
, and that happens dynamically.
Is it slower than plain three.js?
No. There is no additional overhead. Components participate in a unified renderloop outside of React. It outperforms three.js in scale due to Reacts scheduling abilities.
What does it look like?
Let's make a re-usable component that has its own state, reacts to user-input and participates in the render-loop. (live demo). |
import ReactDOM from 'react-dom'
import React, { useRef, useState } from 'react'
import { Canvas, useFrame } from '@react-three/fiber'
function Box(props) {
// This reference will give us direct access to the mesh
const mesh = useRef()
// Set up state for the hovered and active state
const [hovered, setHover] = useState(false)
const [active, setActive] = useState(false)
// Subscribe this component to the render-loop, rotate the mesh every frame
useFrame((state, delta) => (mesh.current.rotation.x += 0.01))
// Return view, these are regular three.js elements expressed in JSX
return (
<mesh
{...props}
ref={mesh}
scale={active ? 1.5 : 1}
onClick={(event) => setActive(!active)}
onPointerOver={(event) => setHover(true)}
onPointerOut={(event) => setHover(false)}
>
<boxGeometry args={[1, 1, 1]} />
<meshStandardMaterial color={hovered ? 'hotpink' : 'orange'} />
</mesh>
)
}
ReactDOM.render(
<Canvas>
<ambientLight />
<pointLight position={[10, 10, 10]} />
<Box position={[-1.2, 0, 0]} />
<Box position={[1.2, 0, 0]} />
</Canvas>,
document.getElementById('root')
)
Show TypeScript example
npm install @types/three
import * as THREE from 'three'
import ReactDOM from 'react-dom'
import React, { useRef, useState } from 'react'
import { Canvas, useFrame } from '@react-three/fiber'
function Box(props: JSX.IntrinsicElements['mesh']) {
const mesh = useRef<THREE.Mesh>(null!)
const [hovered, setHover] = useState(false)
const [active, setActive] = useState(false)
useFrame((state, delta) => (mesh.current.rotation.x += 0.01))
return (
<mesh
{...props}
ref={mesh}
scale={active ? 1.5 : 1}
onClick={(event) => setActive(!active)}
onPointerOver={(event) => setHover(true)}
onPointerOut={(event) => setHover(false)}
>
<boxGeometry args={[1, 1, 1]} />
<meshStandardMaterial color={hovered ? 'hotpink' : 'orange'} />
</mesh>
)
}
ReactDOM.render(
<Canvas>
<ambientLight />
<pointLight position={[10, 10, 10]} />
<Box position={[-1.2, 0, 0]} />
<Box position={[1.2, 0, 0]} />
</Canvas>,
document.getElementById('root')
)
Live demo: https://codesandbox.io/s/icy-tree-brnsm?file=/src/App.tsx
Show React Native example
This example uses expo-cli
for the sake of simplicity, but you can use your own barebones setup if you wish.
# Install expo-cli, this will create our app
npm install expo-cli -g
# Create app and cd into it
expo init my-app
cd my-app
# Install dependencies
npm install three @react-three/fiber@beta react@beta
# Start
expo start
import * as THREE from 'three'
import React, { useRef, useState } from 'react'
import { Canvas, useFrame } from '@react-three/fiber/native'
function Box(props) {
const mesh = useRef(null)
const [hovered, setHover] = useState(false)
const [active, setActive] = useState(false)
useFrame((state, delta) => (mesh.current.rotation.x += 0.01))
return (
<mesh
{...props}
ref={mesh}
scale={active ? 1.5 : 1}
onClick={(event) => setActive(!active)}
onPointerOver={(event) => setHover(true)}
onPointerOut={(event) => setHover(false)}
>
<boxGeometry args={[1, 1, 1]} />
<meshStandardMaterial color={hovered ? 'hotpink' : 'orange'} />
</mesh>
)
}
export default function App() {
return (
<Canvas>
<ambientLight />
<pointLight position={[10, 10, 10]} />
<Box position={[-1.2, 0, 0]} />
<Box position={[1.2, 0, 0]} />
</Canvas>
)
}
Fundamentals
You need to be versed in both React and three.js
before rushing into this. If you are unsure about React consult the official React docs, especially the section about hooks. As for three.js
, make sure you at least glance over the following links:
- Make sure you have a basic grasp of
three.js
. Keep that site open. - When you know what a scene is, a camera, mesh, geometry, material, fork the demo above.
- Look up the JSX elements that you see (mesh, ...), all three.js exports are native to React Three Fiber.
- Try changing some values, scroll through our API to see what the various settings and hooks do.
Some reading material:
- three.js-docs
- three.js-examples
- three.js-fundamentals
- Discover three.js
- Do's and don'ts for performance and best practices
- react-three-fiber alligator.io tutorial by @dghez_
Ecosystem
@react-three/gltfjsx
– turns GLTFs into JSX components@react-three/drei
– useful helpers for react-three-fiber@react-three/postprocessing
– post-processing effects@react-three/flex
– flexbox for react-three-fiber@react-three/xr
– VR/AR controllers and events@react-three/cannon
– physics based hooks@react-three/a11y
– accessibility tools for react-three-fiberzustand
– state managementreact-spring
– a spring-physics-based animation libraryreact-use-gesture
– mouse/touch gestures
How to contribute
If you like this project, please consider helping out. All contributions are welcome as well as donations to Opencollective, or in crypto BTC: 36fuguTPxGCNnYZSRdgdh6Ea94brCAjMbH
, ETH: 0x6E3f79Ea1d0dcedeb33D3fC6c34d2B1f156F2682
.