rafDriversSince 0.6.0

rafDrivers allow you to control when and how often computations in Theatre tick forward. (raf stands for requestAnimationFrame).

The default rafDriver in Theatre creates a raf loop and ticks forward on each frame. You can create your own rafDriver, which enables the following use-cases:

  1. When using Theatre.js alongside other animation libs (@react-three/fiber/gsap/lenis/etc), you'd want all animation libs to use a single raf loop to keep the libraries in sync and also to get better performance.
  2. In XR sessions, you'd want Theatre to tick forward using xr.requestAnimationFrame().
  3. In some advanced cases, you'd just want to manually tick forward (many ticks per frame, or skipping many frames, etc). This is useful for recording an animation, rendering to a file, testing an animation, running benchmarks, etc.

Here is how you'd create a custom rafDriver:

import { createRafDriver } from '@theatre/core'
const rafDriver = createRafDriver({ name: 'a custom 5fps raf driver' })
setInterval(() => {
}, 200)

Now, any time you set up an onChange() listener, pass your custom rafDriver:

import { onChange } from '@theatre/core'
// let's say object is a Theatre object, the one returned from calling `sheet.object()`
// this callback will now only be called at 5fps (and won't be called if there are no new values)
// even if `sequence.play()` updates `object.props` at 60fps, this listener is called a maximum of 5fps
(propValues) => {
// this will update the values of `object.props` at 60fps, but the listener above will still get called a maximum of 5fps
// we can also customize at what resolution the sequence's playhead moves forward
sheet.sequence.play({ rafDriver }) // the playhead will move forward at 5fps

You can optionally make studio use this rafDriver. This means the parts of the studio that tick based on raf, will now tick at 5fps. This is only useful if you're doing something crazy like running the studio (and not the core) in an XR frame.

__experimental_rafDriver: rafDriver,

rafDrivers can optionally provide a start/stop callback. Theatre will call start() when it actually has computations scheduled, and will call stop if there is nothing to update after a few ticks:

import { createRafDriver } from '@theatre/core'
import type { IRafDriver } from '@theare/core'
function createBasicRafDriver(): IRafDriver {
let rafId: number | null = null
const start = (): void => {
if (typeof window !== 'undefined') {
const onAnimationFrame = (t: number) => {
rafId = window.requestAnimationFrame(onAnimationFrame)
rafId = window.requestAnimationFrame(onAnimationFrame)
} else {
setTimeout(() => driver.tick(1), 0)
const stop = (): void => {
if (typeof window !== 'undefined') {
if (rafId !== null) {
} else {
// nothing to do in SSR
const driver = createRafDriver({ name: 'DefaultCoreRafDriver', start, stop })
return driver

rafDrivers in@theatre/r3f

You can instruct @theatre/r3f to use your custom rafDriver by wrapping your react tree in <RafDriverProvider>:

import { RafDriverProvider } from '@theatre/r3f'
import { createRafDriver } from '@theatre/core'
const myCustomRafDriver = createRafDriver({ name: 'my custom raf driver', start, stop })
function App() {
return (
<RafDriverProvider driver={myCustomRafDriver}>
<SomeComponent />
{/* you can use several rafDrivers on the same page */}
<RafDriverProvider driver={anotherRaDriver}>
<AnotherComponent />

Was this article helpful to you?

Last edited on February 01, 2024.
Edit this page

Theatre.js is a design tool in the making. We aim to blur the line between designer/developer, author/consumer, and artist/scientist.
© 2022 Theatre.js Oy – Helsinki.