Canvas

The Canvas object is your portal into three.js

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

PropDescriptionDefault
childrenthree.js JSX elements or regular components
fallbackoptional DOM JSX elements or regular components in case GL is not supported
glRenderer props, an instance, or a sync/async factory. See Renderers.{}
cameraProps that go into the default camera, or your own THREE.Camera{ fov: 75, near: 0.1, far: 1000, position: [0, 0, 5] }
sceneProps that go into the default scene, or your own THREE.Scene{}
shadowsProps that go into gl.shadowMap, can be set true for PCFsoft or one of the following: 'basic', 'percentage', 'soft', 'variance'false
raycasterProps that go into the default raycaster{}
frameloopRender mode: always, demand, neveralways
resizeResize config, see react-use-measure's options{ scroll: true, debounce: { scroll: 50, resize: 0 } }
orthographicCreates an orthographic camerafalse
dprPixel-ratio, use window.devicePixelRatio, or automatic: [min, max][1, 2]
legacyEnables THREE.ColorManagement in three r139 or laterfalse
linearSwitch off automatic sRGB color space and gamma correctionfalse
eventsConfiguration for the event manager, as a function of stateimport { events } from "@react-three/fiber"
eventSourceThe source where events are being subscribed to, HTMLElementReact.RefObject<HTMLElement>, gl.domElement.parentNode
eventPrefixThe event prefix that is cast into canvas pointer x/y eventsoffset
flatUse THREE.NoToneMapping instead of THREE.ACESFilmicToneMappingfalse
onCreatedCallback after the canvas has rendered (but not yet committed)(state) => {}
onPointerMissedResponse 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.Perspective camera
  • A THREE.Orthographic cam if orthographic is true
  • A THREE.PCFSoftShadowMap if shadows is true
  • A THREE.Scene (into which all the JSX is rendered) and a THREE.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>
  )
}
Note

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 valueOwnership
Omitted or a props objectR3F owns the renderer it creates and disposes it on unmount.
A sync or async factoryR3F owns the returned renderer and disposes it on unmount.
A renderer instanceYou 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>,
)