Canvas
The Canvas object is where you start to define your React Three Fiber Scene.
import React from 'react'
import { Canvas } from '@react-three/fiber'
const App = () => (
<Canvas>
<pointLight position={[10, 10, 10]} />
<mesh>
<sphereGeometry />
<meshStandardMaterial color="hotpink" />
</mesh>
</Canvas>
)
Properties
| Prop | Description | Default |
|---|---|---|
| children | three.js JSX elements or regular components | |
| fallback | optional DOM JSX elements or regular components in case GL is not supported | |
| gl | Renderer props, an instance, or a sync/async factory. See Renderers. | {} |
| camera | Props that go into the default camera, or your own THREE.Camera | { fov: 75, near: 0.1, far: 1000, position: [0, 0, 5] } |
| scene | Props that go into the default scene, or your own THREE.Scene | {} |
| shadows | Props that go into gl.shadowMap, can be set true for PCFsoft or one of the following: 'basic', 'percentage', 'soft', 'variance' | false |
| raycaster | Props that go into the default raycaster | {} |
| frameloop | Render mode: always, demand, never | always |
| resize | Resize config, see react-use-measure's options | { scroll: true, debounce: { scroll: 50, resize: 0 } } |
| orthographic | Creates an orthographic camera | false |
| dpr | Pixel-ratio, use window.devicePixelRatio, or automatic: [min, max] | [1, 2] |
| legacy | Enables THREE.ColorManagement in three r139 or later | false |
| linear | Switch off automatic sRGB color space and gamma correction | false |
| events | Configuration for the event manager, as a function of state | import { events } from "@react-three/fiber" |
| eventSource | The source where events are being subscribed to, HTMLElement | React.RefObject<HTMLElement>, gl.domElement.parentNode |
| eventPrefix | The event prefix that is cast into canvas pointer x/y events | offset |
| flat | Use THREE.NoToneMapping instead of THREE.ACESFilmicToneMapping | false |
| onCreated | Callback after the canvas has rendered (but not yet committed) | (state) => {} |
| onPointerMissed | Response for pointer clicks that have missed any target | (event) => {} |
Updating configuration
Canvas initializes settings from its props and defaults. Subsequent changes to dpr, frameloop, performance, and shadows apply the new prop value. Rerendering with the same values preserves runtime changes made through setters such as setDpr and setFrameloop, or directly to gl.shadowMap. Equivalent inline DPR arrays and shadow or performance option objects count as unchanged.
Removing dpr, frameloop, or shadows applies its default if that differs from the previous prop value. Performance options merge into the current settings; removing performance leaves those settings intact, and supplying it again applies it again.
Size changes update the renderer and camera without reapplying unrelated settings. These rules also apply to repeated root.configure() calls. Changing a DPR range recalculates its pixel ratio; an unchanged range does not overwrite a runtime DPR override.
Defaults
Canvas uses createRoot which will create a translucent THREE.WebGLRenderer with the following constructor args:
- antialias=true
- alpha=true
- powerPreference="high-performance"
and with the following properties:
- outputColorSpace = THREE.SRGBColorSpace
- toneMapping = THREE.ACESFilmicToneMapping
It will also create the following scene internals:
- A
THREE.Perspectivecamera - A
THREE.Orthographiccam iforthographicis true - A
THREE.PCFSoftShadowMapifshadowsis true - A
THREE.Scene(into which all the JSX is rendered) and aTHREE.Raycaster
In recent versions of threejs, THREE.ColorManagement.enabled will be set to true to enable automatic conversion of colors according to the renderer's configured color space. R3F will handle texture color space conversion. For more on this topic, see https://threejs.org/manual/#en/color-management.
Errors and fallbacks
On some systems WebGL may not be supported, you can provide a fallback component that will be rendered instead of the canvas:
<Canvas fallback={<div>Sorry no WebGL supported!</div>}>
<mesh />
</Canvas>
You should also safeguard the canvas against WebGL context crashes, for instance if users have the GPU disabled or GPU drivers are faulty.
import { useErrorBoundary } from 'use-error-boundary'
function App() {
const { ErrorBoundary, didCatch, error } = useErrorBoundary()
return didCatch ? (
<div>{error.message}</div>
) : (
<ErrorBoundary>
<Canvas>
<mesh />
</Canvas>
</ErrorBoundary>
)
}
Ideally, and if possible, your fallback is a seamless, visual replacement for what the canvas would have otherwise rendered.
Renderers
Canvas creates a THREE.WebGLRenderer by default. Use the gl prop to configure it or supply your own renderer. These options also apply to createRoot through root.configure({ gl }).
To configure the default renderer, pass an object with renderer constructor options or properties:
<Canvas gl={{ antialias: false }} />
Custom renderers
Pass a factory function to create a custom renderer. R3F passes it the default constructor options, including the canvas:
<Canvas gl={(defaults) => new WebGLRenderer({ ...defaults, antialias: false })} />
You can also pass an existing renderer instance directly:
<Canvas gl={renderer} />
A factory can return a promise. R3F waits for that promise before rendering the scene. A renderer created with a factory function is owned by R3F and will automatically be disposed while an existing renderer instance is owned by the user and R3F will not control its lifecycle.
WebGPU
To use WebGPURenderer, import it from three/webgpu and initialize it in an async gl factory:
import * as THREE from 'three/webgpu'
import { Canvas, extend, type ThreeToJSXElements } from '@react-three/fiber'
declare module '@react-three/fiber' {
interface ThreeElements extends ThreeToJSXElements<typeof THREE> {}
}
extend(THREE)
export default () => (
<Canvas
gl={async (props) => {
const renderer = new THREE.WebGPURenderer(props as any)
await renderer.init()
return renderer
}}>
<mesh>
<meshBasicNodeMaterial />
<boxGeometry />
</mesh>
</Canvas>
)
Ownership
How you supply the renderer determines who is responsible for disposing it:
gl value | Ownership |
|---|---|
| Omitted or a props object | R3F owns the renderer it creates and disposes it on unmount. |
| A sync or async factory | R3F owns the returned renderer and disposes it on unmount. |
| A renderer instance | You own the renderer and are responsible for calling dispose(). |
Return a renderer dedicated to that root from a factory, including when using WebGPURenderer. Passing an instance directly keeps disposal under your control. In v9, unmounting still releases its WebGL context when supported, even though R3F does not call its dispose() method.
Custom Canvas
R3F can render to a root, similar to how react-dom and all the other React renderers work. This allows you to shave off react-dom (~40kb), react-use-measure (~3kb) and, if you don't need them, pointer-events (~7kb) (you need to explicitly import events and add them to the config otherwise).
Roots have the same options and properties as Canvas, but you are responsible for resizing it. It requires an existing DOM <canvas> object into which it renders.
CreateRoot
Creates a root targeting a canvas, rendering JSX.
import * as THREE from 'three'
import { extend, createRoot, events } from '@react-three/fiber'
// Register the THREE namespace as native JSX elements.
// See below for notes on tree-shaking
extend(THREE)
// Create a react root
const root = createRoot(document.querySelector('canvas'))
async function app() {
// Configure the root, inject events optionally, set camera, etc
// This *must* be called before render. It runs synchronously unless `gl` is an
// async factory (e.g. WebGPU), so awaiting it is only required in that case
await root.configure({ events, camera: { position: [0, 0, 50] } })
// createRoot by design is not responsive, you have to take care of resize yourself
window.addEventListener('resize', () => {
root.configure({ size: { width: window.innerWidth, height: window.innerHeight } })
})
// Trigger resize
window.dispatchEvent(new Event('resize'))
// Render entry point. Once the root is configured the first render mounts synchronously,
// with the same caveats as `flushSync`. Before that it waits for `root.ready`
root.render(<App />)
// Unmount and dispose of memory
// root.unmount()
}
app()
root.ready is a promise that is pending only while an async renderer is being created. It carries a status field, so it can be passed to React's use to suspend until the renderer is available.
Tree-shaking
New with v8, the underlying reconciler no longer pulls in the THREE namespace automatically.
This enables a granular catalogue which also enables tree-shaking via the extend API:
import { extend, createRoot } from '@react-three/fiber'
import { Mesh, BoxGeometry, MeshStandardMaterial } from 'three'
extend({ Mesh, BoxGeometry, MeshStandardMaterial })
createRoot(canvas).render(
<>
<mesh>
<boxGeometry />
<meshStandardMaterial />
</mesh>
</>,
)
There's an official babel plugin which will do this for you automatically:
// In:
import { createRoot } from '@react-three/fiber'
createRoot(canvasNode).render(
<mesh>
<boxGeometry />
<meshStandardMaterial />
</mesh>,
)
// Out:
import { createRoot, extend } from '@react-three/fiber'
import { Mesh as _Mesh, BoxGeometry as _BoxGeometry, MeshStandardMaterial as _MeshStandardMaterial } from 'three'
extend({
Mesh: _Mesh,
BoxGeometry: _BoxGeometry,
MeshStandardMaterial: _MeshStandardMaterial,
})
createRoot(canvasNode).render(
<mesh>
<boxGeometry />
<meshStandardMaterial />
</mesh>,
)