Home
cd ../playbooks
Developer ToolsIntermediate

React Three Fiber Animation Reference

Animate React Three Fiber scenes correctly — useFrame fundamentals with render priority, full GLTF animation control via useAnimations (playback, crossfade, event listeners, speed-based blending between Idle/Walk/Run), @react-spring/three physics animations with gesture and chain integration, and the five performance rules that keep per-frame updates from triggering React re-renders.

10 minutes
By zebbern (claude-code-guide)Source
#react-three-fiber#threejs#react#3d-animation#webgl#react-spring

Calling setState inside a React Three Fiber useFrame callback re-renders the entire React component tree sixty times a second — the fix (mutate the mesh ref directly instead) is one line, but it's the single most common performance mistake in every R3F codebase that started as a normal React app.

Who it's for: React developers building 3D scenes and animations with React Three Fiber, engineers implementing character animation blending (Idle/Walk/Run) from GLTF models, teams choosing between useFrame manual animation and @react-spring/three physics-based springs, anyone debugging janky R3F performance caused by React re-renders inside the animation loop

Example

"Blend between idle, walk, and run animations based on character speed" → useAnimations wired to a GLTF's action clips, all three animations started simultaneously, and their effective weights cross-blended every frame inside useFrame based on a speed threshold — the correct GLTF animation-blending pattern instead of abruptly switching between separate play() calls

CLAUDE.md Template

New here? 3-minute setup guide → | Already set up? Copy the template below.

# React Three Fiber Animation

Animate objects in React Three Fiber (R3F) — `useFrame`, GLTF animations with `useAnimations`, spring physics with `@react-spring/three`, morph targets, skeletal animation, procedural motion, and physics-based movement.

## Quick Start

```tsx
import { Canvas, useFrame } from '@react-three/fiber'
import { useRef } from 'react'

function RotatingBox() {
  const meshRef = useRef()

  useFrame((state, delta) => {
    meshRef.current.rotation.x += delta
    meshRef.current.rotation.y += delta * 0.5
  })

  return (
    <mesh ref={meshRef}>
      <boxGeometry />
      <meshStandardMaterial color="hotpink" />
    </mesh>
  )
}

export default function App() {
  return (
    <Canvas>
      <ambientLight />
      <RotatingBox />
    </Canvas>
  )
}
```

## useFrame — The Core Animation Hook

Runs every frame. The `state` object carries `clock`, `camera`, `scene`, `gl`, `mouse`/`pointer`, `viewport`, `size`, `raycaster`, `get`/`set`, and `invalidate` (for `frameloop="demand"`):

```tsx
useFrame((state, delta) => {
  const t = state.clock.getElapsedTime()
  meshRef.current.position.y = Math.sin(t) * 2
})
```

**Render priority** — lower numbers run first; default is 0. Use negative for pre-render, positive for post-render: `useFrame(() => {...}, -1)`.

**Conditional animation** — gate the update inside the callback rather than conditionally calling the hook:

```tsx
useFrame((state, delta) => {
  if (!isAnimating) return
  meshRef.current.rotation.y += delta
})
```

## GLTF Animations with useAnimations

The recommended way to play animations from GLTF/GLB files.

```tsx
import { useGLTF, useAnimations } from '@react-three/drei'
import { useEffect, useRef } from 'react'

function AnimatedModel() {
  const group = useRef()
  const { scene, animations } = useGLTF('/models/character.glb')
  const { actions, names } = useAnimations(animations, group)

  useEffect(() => {
    actions[names[0]]?.play()
  }, [actions, names])

  return <primitive ref={group} object={scene} />
}
```

### Animation Control

```tsx
const action = actions['Walk']
action.play()
action.stop()
action.reset()
action.paused = true

action.timeScale = 1.5   // 1.5x speed
action.timeScale = -1    // reverse

action.loop = THREE.LoopOnce
action.loop = THREE.LoopRepeat
action.loop = THREE.LoopPingPong
action.repetitions = 3
action.clampWhenFinished = true

action.weight = 1        // for blending
```

### Crossfade Between Animations

```tsx
useEffect(() => {
  Object.values(actions).forEach(action => action?.fadeOut(0.5))
  actions[currentAnim]?.reset().fadeIn(0.5).play()
}, [currentAnim, actions])
```

### Animation Events

```tsx
useEffect(() => {
  const onFinished = (e) => console.log('Animation finished:', e.action.getClip().name)
  const onLoop = (e) => console.log('Animation looped:', e.action.getClip().name)
  mixer.addEventListener('finished', onFinished)
  mixer.addEventListener('loop', onLoop)
  return () => {
    mixer.removeEventListener('finished', onFinished)
    mixer.removeEventListener('loop', onLoop)
  }
}, [mixer])
```

### Animation Blending (Idle/Walk/Run by Speed)

```tsx
useEffect(() => {
  actions['Idle']?.play()
  actions['Walk']?.play()
  actions['Run']?.play()
}, [actions])

useFrame(() => {
  if (speed < 0.1) {
    actions['Idle']?.setEffectiveWeight(1)
    actions['Walk']?.setEffectiveWeight(0)
    actions['Run']?.setEffectiveWeight(0)
  } else if (speed < 5) {
    const t = speed / 5
    actions['Idle']?.setEffectiveWeight(1 - t)
    actions['Walk']?.setEffectiveWeight(t)
    actions['Run']?.setEffectiveWeight(0)
  } else {
    const t = Math.min((speed - 5) / 5, 1)
    actions['Idle']?.setEffectiveWeight(0)
    actions['Walk']?.setEffectiveWeight(1 - t)
    actions['Run']?.setEffectiveWeight(t)
  }
})
```

## Spring Animation (@react-spring/three)

Physics-based spring animations that integrate with R3F.

```bash
npm install @react-spring/three
```

### Basic Spring

```tsx
import { useSpring, animated } from '@react-spring/three'

function AnimatedBox() {
  const [active, setActive] = useState(false)
  const { scale, color } = useSpring({
    scale: active ? 1.5 : 1,
    color: active ? '#ff6b6b' : '#4ecdc4',
    config: { mass: 1, tension: 280, friction: 60 }
  })

  return (
    <animated.mesh scale={scale} onClick={() => setActive(!active)}>
      <boxGeometry />
      <animated.meshStandardMaterial color={color} />
    </animated.mesh>
  )
}
```

### Config Presets

```tsx
import { config } from '@react-spring/three'
// Presets: config.default, config.gentle, config.wobbly, config.stiff, config.slow, config.molasses

// Or fully custom:
{ mass: 1, tension: 170, friction: 26, clamp: false, precision: 0.01, velocity: 0 }
```

### Multiple Springs

```tsx
import { useSprings, animated } from '@react-spring/three'

function AnimatedBoxes({ count = 5 }) {
  const [springs, api] = useSprings(count, (i) => ({
    position: [i * 2 - count, 0, 0],
    scale: 1,
    config: { mass: 1, tension: 280, friction: 60 }
  }))

  const handleClick = (index) => {
    api.start((i) => (i === index ? { scale: 1.5 } : { scale: 1 }))
  }

  return springs.map((spring, i) => (
    <animated.mesh key={i} position={spring.position} scale={spring.scale} onClick={() => handleClick(i)}>
      <boxGeometry />
      <meshStandardMaterial color="orange" />
    </animated.mesh>
  ))
}
```

### Gesture Integration

```tsx
import { useSpring, animated } from '@react-spring/three'
import { useDrag } from '@use-gesture/react'

function DraggableBox() {
  const [spring, api] = useSpring(() => ({
    position: [0, 0, 0],
    config: { mass: 1, tension: 280, friction: 60 }
  }))

  const bind = useDrag(({ movement: [mx, my], down }) => {
    api.start({ position: down ? [mx / 100, -my / 100, 0] : [0, 0, 0] })
  })

  return (
    <animated.mesh {...bind()} position={spring.position}>
      <boxGeometry />
      <meshStandardMaterial color="hotpink" />
    </animated.mesh>
  )
}
```

### Chained Animations

```tsx
import { useSpring, animated, useChain, useSpringRef } from '@react-spring/three'

function ChainedAnimation() {
  const scaleRef = useSpringRef()
  const rotationRef = useSpringRef()

  const { scale } = useSpring({
    ref: scaleRef, from: { scale: 0 }, to: { scale: 1 },
    config: { tension: 200, friction: 20 }
  })
  const { rotation } = useSpring({
    ref: rotationRef, from: { rotation: [0, 0, 0] }, to: { rotation: [0, Math.PI * 2, 0] },
    config: { tension: 100, friction: 30 }
  })

  // Scale first (0-0.5 of the chain), then rotation (0.5-1)
  useChain([scaleRef, rotationRef], [0, 0.5])

  return (
    <animated.mesh scale={scale} rotation={rotation}>
      <boxGeometry />
      <meshStandardMaterial color="cyan" />
    </animated.mesh>
  )
}
```

## Performance Tips

1. **Isolate animated components** — split animated meshes into their own components so a parent's re-render doesn't cascade into every child; only the animated mesh should re-render.
2. **Use refs over state** — mutate `mesh.current.rotation`/`position` directly inside `useFrame` instead of `setState`, which would trigger a full React re-render every frame.
3. **Throttle expensive calculations** — accumulate `delta` and only run costly logic every N milliseconds, while still updating cheap properties every frame:

```tsx
function ThrottledAnimation() {
  const meshRef = useRef()
  const accumulated = useRef(0)

  useFrame((state, delta) => {
    accumulated.current += delta
    if (accumulated.current > 0.1) {
      // expensive calculation here, throttled to every 100ms
      accumulated.current = 0
    }
    meshRef.current.rotation.y += delta // cheap, runs every frame
  })
}
```

4. **Pause offscreen animations** — check visibility/frustum before running expensive per-frame updates on something the camera can't see.
5. **Share animation clips** — reuse the same loaded clip across multiple instances instead of re-loading or re-parsing per instance.

## When NOT to Use This

General Three.js animation outside React (use vanilla Three.js patterns), CSS/DOM animation for non-3D UI elements, or 2D canvas animation unrelated to a WebGL scene.

Get new playbooks like this one

One email a week with new Claude Code workflows. Free, like everything here.

No spam. Unsubscribe anytime.

README.md

What This Does

A working code reference for animating React Three Fiber scenes across every common technique: useFrame fundamentals (the per-frame state object, render priority ordering via the second argument, and conditional animation done correctly inside the callback rather than conditionally calling the hook), full GLTF character animation via useAnimations — playback control (play/stop/reset/pause/speed/loop mode), crossfading between clips, listening for finished/loop mixer events, and the speed-based weight-blending pattern that smoothly transitions between Idle, Walk, and Run clips instead of abruptly switching between them.

The spring-physics half covers @react-spring/three from basic single-value springs through multi-spring arrays, gesture-driven dragging via @use-gesture/react, and chained animations (useChain) that sequence one spring after another with precise timing overlap. It closes with five concrete performance rules — isolate animated components so re-renders don't cascade, mutate refs instead of calling setState inside useFrame, throttle expensive per-frame calculations via delta accumulation while keeping cheap updates running every frame, pause offscreen animations, and share animation clips across instances — the exact fixes for the performance mistakes that show up first in a normal React developer's initial R3F code.


Quick Start

Step 1: Create a Project Folder

mkdir r3f-animation && cd r3f-animation

Step 2: Download the Template

Click Download above, then:

mv ~/Downloads/CLAUDE.md ./

Step 3: Animate a Scene

claude

Ask Claude to animate an object, play a GLTF character animation, add a spring-based hover or drag interaction, or fix janky performance in an existing R3F scene. It will use useFrame or @react-spring/three correctly for the situation and apply the performance rules by default.


Tips & Best Practices

  • Default to mutating refs inside useFrame rather than useState for anything animating every frame — treat a setState call inside the animation loop as a performance bug to catch in review, not a stylistic choice.
  • For character animation, prefer the weight-blending pattern (all relevant clips playing simultaneously with setEffectiveWeight cross-fading between them) over abrupt play()/stop() switches — it's what produces smooth transitions between states like idle and walking.
  • Reach for @react-spring/three specifically when an animation needs to be interruptible mid-motion (a drag gesture, a hover that can re-trigger before finishing) — springs naturally handle interruption in a way fixed-duration useFrame tweening doesn't.

Limitations

  • Covers React Three Fiber and Drei/react-spring integration specifically — vanilla Three.js animation patterns outside React aren't the same API surface, even though the underlying concepts (mixers, action weights, clips) are shared.
  • GLTF animation examples assume the model was exported with named animation clips; a model without clean clip names will need those identified first (e.g. by logging names from useAnimations).
  • Performance guidance targets typical scene complexity; a scene with very large numbers of simultaneously animated instances may need additional techniques (instancing, GPU-driven animation) beyond what's covered here.

$Related Playbooks

Developer Tools

Reshape PR Commits

Turn a branch's messy WIP/fixup commit history into a clean, logically-grouped set of commits before review — proposed regrouping with explicit approval, git reset --soft plus staged re-commits instead of interactive rebase, and hard guardrails (feature branches only, force-with-lease only, never touch another author's commits).

5 minutes
Intermediate
Developer Tools

Simplicity First Code Gate

A hard correctness gate against over-complex or oversized code — one ordering principle (human readability first, agent traceability second), five enforceable rules, and a pre-finish checklist that treats unnecessary complexity as a bug, not a style opinion.

2 minutes
Beginner
Developer Tools

Slidev Presentation Builder

Build developer-focused presentations with Slidev — Markdown-driven slides with live code, syntax highlighting, Monaco editor embeds, Mermaid/PlantUML diagrams, LaTeX math, click-based animations, and presenter notes, plus a quick-reference table for the exact syntax each feature needs.

5 minutes
Beginner
Developer Tools

Technical Writing Style Guide

Write docstrings, READMEs, commit messages, and PR descriptions that read like human technical documentation instead of LLM output — six concrete 'tells' with before/after rewrites, a smoothness diagnostic for prose that sounds authoritative while stating one fact three times, and industry-metaphor substitution tables ('surfaces' to 'raises/returns/logs', 'wired through' to 'passed as a parameter').

5 minutes
Intermediate
Developer Tools

Secret Scan and Rotation

Find committed credentials in a repository's working tree and full history, triage real secrets from test fixtures, and drive rotation-first remediation — with an absolute rule against ever printing a secret's actual value, even during the scan itself.

5 minutes
Intermediate
Developer Tools

Self-Improvement Loop Design

Design systems where the harness itself is the optimization target — an optimization ladder from prompt to context to workflow to harness code, a two-split empirical acceptance gate, an outside-the-loop invariant for the evaluator, and a catalog of documented reward-hacking and collapse failure modes.

10 minutes
Advanced
Developer Tools

Skill Security Inspector

Review an AI agent skill before installing it using two independent lines — static scanner evidence plus source-aware semantic judgment — checking purpose fit, permission fit, sensitive access, external transmission, execution risk, and persistence, down to a clear APPROVE, CAUTION, or REJECT verdict.

10 minutes
Intermediate
Developer Tools

Semantic Prompt Compression

Re-encode verbose system prompts, tool descriptions, and skill bodies into a dense telegraphic register — punctuation as connectives, label frames, verbless assertions — via re-encoding, not word deletion, with a density gate and a declared-loss verification pass.

5 minutes
Advanced
Developer Tools

Simplified Technical English for Docs

Write or rewrite technical documentation with the ASD-STE100 Simplified Technical English discipline — the aerospace maintenance-manual standard adapted for READMEs, runbooks, error messages, incident reports, and agent instructions.

10 minutes
Intermediate
Developer Tools

Subagent-Driven Development

Execute an implementation plan by dispatching a fresh subagent per task with a spec-and-quality review after each, a ledger that survives compaction, and a 'rulings not stalls' policy that keeps a running plan from waiting on a human at every fork.

10 minutes
Advanced
Developer Tools

Secure Coding Practices

A threat-model-first secure coding reference — trust-boundary mapping, a STRIDE quick-pass, a three-tier always/ask-first/never boundary system, and copy-paste prevention patterns for injection, XSS, broken access control, and SSRF.

10 minutes
Intermediate
Developer Tools

Security Guidance Review

Three-layer continuous security review for AI-generated code — instant regex warnings on edit, an LLM diff review at end of turn, and an agentic commit-time reviewer that traces data flow across files.

10 minutes
Advanced

Browse all Developer Tools playbooks →