Building your First Timeline.

Step-by-step - create a React scene and orchestrate camera behavior with a timeline.

This walks you through building the intro example from scratch.

Final result

import { Canvas, useThree } from '@react-three/fiber'
import { Text, Environment } from '@react-three/drei'
import { useRef } from 'react'
import { Mesh } from 'three'
import { useTimeline, action, lookAt, spring, springPresets } from '@react-three/timeline'

function Scene() {
const camera = useThree((s) => s.camera)
const red = useRef<Mesh>(null)
const blue = useRef<Mesh>(null)

useTimeline(async function* () {
  while (true) {
    yield* action({ update: lookAt(camera, red.current!, spring(springPresets.stiff)) })
    yield* action({ update: lookAt(camera, blue.current!, spring(springPresets.stiff)) })
  }
}, [])

return (
  <>
    <Text color="black" position-y={1} scale={0.3}>Your first timeline</Text>
    <mesh ref={red} position-x={-2} position-y={-1} rotation-y={(-30 / 180) * Math.PI} scale={[0.2, 0.2, 0.4]}>
      <sphereGeometry />
      <meshPhysicalMaterial emissive="red" emissiveIntensity={1.5} color="red" />
    </mesh>
    <mesh ref={blue} position-x={2} position-y={-1} rotation-y={(20 / 180) * Math.PI} scale={[0.2, 0.2, 0.4]}>
      <sphereGeometry />
      <meshPhysicalMaterial emissive="blue" emissiveIntensity={5} color="blue" />
    </mesh>
  </>
)
}

export default function App() {
return (
  <Canvas style={{ position: "absolute", inset: "0", touchAction: "none" }}>
    <Environment
      backgroundIntensity={0.1}
      backgroundRotation={[0, (90 / 180) * Math.PI, 0]}
      preset="studio"
      background
      blur={0.1}
    />
    <Scene />
  </Canvas>
)
}

Build it in steps

  1. Install
pnpm add three @react-three/fiber @react-three/drei @react-three/timeline
  1. Minimal scene

Create App.tsx and render a scene with two meshes.

  1. The scene shell
// App.tsx
import { Canvas } from '@react-three/fiber'

export default function App() {
  return (
    <Canvas style={{ position: "absolute", inset: "0", touchAction: "none" }}>
      {/* Add <Scene /> in the next step */}
    </Canvas>
  )
}
  1. Add meshes and references
import { useRef } from 'react'
import { Mesh } from 'three'
import { Text } from '@react-three/drei'

function Scene() {
  const red = useRef<Mesh>(null)
  const blue = useRef<Mesh>(null)
  return (
    <>
      <Text color="black" position-y={1} scale={0.3}>Your first timeline</Text>
      <mesh ref={red} position-x={-2} position-y={-1} rotation-y={(-30/180)*Math.PI} scale={[0.2,0.2,0.4]}>
        <sphereGeometry />
        <meshPhysicalMaterial emissive="red" emissiveIntensity={1.5} color="red" />
      </mesh>
      <mesh ref={blue} position-x={2} position-y={-1} rotation-y={(20/180)*Math.PI} scale={[0.2,0.2,0.4]}>
        <sphereGeometry />
        <meshPhysicalMaterial emissive="blue" emissiveIntensity={5} color="blue" />
      </mesh>
    </>
  )
}
  1. Add the timeline

We use useTimeline to run a while (true) loop, which keeps the sequence going, and each yield* action({ update: lookAt(...) }) eases the camera toward a target using a spring; once the ease completes, the next action starts.

import { useThree } from '@react-three/fiber'
import { useTimeline, action, lookAt, spring, springPresets } from '@react-three/timeline'

function Scene() {
  const camera = useThree((s) => s.camera)
  const red = useRef<Mesh>(null)
  const blue = useRef<Mesh>(null)

  useTimeline(async function* () {
    while (true) {
      yield* action({ update: lookAt(camera, red.current!, spring(springPresets.stiff)) })
      yield* action({ update: lookAt(camera, blue.current!, spring(springPresets.stiff)) })
    }
  }, [])
  // ...return meshes as above
}
  1. Mount the scene
export default function App() {
  return (
    <Canvas style={{ position: "absolute", inset: "0", touchAction: "none" }}>
      <Scene />
    </Canvas>
  )
}