Image to Three.js Model
img2threejs/img2threejs
Rebuilds the object in a reference image as a procedural, animation-ready Three.js model written entirely in code, using staged sculpting with quality checks.
React Three Fiber animation - useFrame, useAnimations, spring physics, keyframes.
$ npx skills add zebbern/claude-code-guide --skill r3f-animation -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install zebbern/claude-code-guide r3f-animation --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/zebbern/claude-code-guide.git skills-src && mkdir -p .claude/skills && cp -r skills-src/skills/r3f-animation .claude/skills/r3f-animation && rm -rf skills-srcUse ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.
Claude Code skills documentation · loads skills from .claude/skills/
Install the "r3f-animation" agent skill from https://github.com/zebbern/claude-code-guide/tree/main/skills/r3f-animation into .claude/skills/r3f-animation/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "r3f-animation", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/zebbern/claude-code-guide/tree/main/skills/r3f-animationType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add zebbern/claude-code-guide --skill r3f-animation -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install zebbern/claude-code-guide r3f-animation --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/zebbern/claude-code-guide.git skills-src && mkdir -p .agents/skills && cp -r skills-src/skills/r3f-animation .agents/skills/r3f-animation && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "r3f-animation" agent skill from https://github.com/zebbern/claude-code-guide/tree/main/skills/r3f-animation into .agents/skills/r3f-animation/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "r3f-animation", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add zebbern/claude-code-guide --skill r3f-animation -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install zebbern/claude-code-guide r3f-animation --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/zebbern/claude-code-guide.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/skills/r3f-animation .cursor/skills/r3f-animation && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "r3f-animation" agent skill from https://github.com/zebbern/claude-code-guide/tree/main/skills/r3f-animation into .cursor/skills/r3f-animation/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "r3f-animation", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/zebbern/claude-code-guide.git --path skills/r3f-animation--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add zebbern/claude-code-guide --skill r3f-animation -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install zebbern/claude-code-guide r3f-animation --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/zebbern/claude-code-guide.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/skills/r3f-animation .gemini/skills/r3f-animation && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "r3f-animation" agent skill from https://github.com/zebbern/claude-code-guide/tree/main/skills/r3f-animation into .gemini/skills/r3f-animation/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "r3f-animation", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install zebbern/claude-code-guide r3f-animationInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add zebbern/claude-code-guide --skill r3f-animation -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/zebbern/claude-code-guide.git skills-src && mkdir -p .github/skills && cp -r skills-src/skills/r3f-animation .github/skills/r3f-animation && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "r3f-animation" agent skill from https://github.com/zebbern/claude-code-guide/tree/main/skills/r3f-animation into .github/skills/r3f-animation/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "r3f-animation", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add zebbern/claude-code-guide --skill r3f-animation -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install zebbern/claude-code-guide r3f-animation --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/zebbern/claude-code-guide.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/skills/r3f-animation .opencode/skills/r3f-animation && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "r3f-animation" agent skill from https://github.com/zebbern/claude-code-guide/tree/main/skills/r3f-animation into .opencode/skills/r3f-animation/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "r3f-animation", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
r3f-animationReact Three Fiber animation - useFrame, useAnimations, spring physics, keyframes.
R3f Animation is an agent skill from zebbern/claude-code-guide. React Three Fiber animation - useFrame, useAnimations, spring physics, keyframes. Use when animating objects, playing GLTF animations, creating procedural motion, or implementing physics-based movement.
Its SKILL.md is about 5.5k tokens, which your agent loads only when the skill is triggered. It is a single SKILL.md file with no bundled scripts.
It sits in Game Development, covering 3D graphics and WebGL. The repository describes itself as: Claude Code Guide - Setup, Commands, workflows, agents, skills & tips-n-tricks from beginner to power user! The licence is MIT.
5 steps, taken from the first numbered list in SKILL.md.
Read from SKILL.md and the folder at commit 9cde898. It shows what the files ask for, not the result of running them.
Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.
From allowed-tools in the SKILL.md frontmatter.
Shell commands in SKILL.md call:
npmFrom the folder's file list and the shell code blocks in SKILL.md.
No URLs in SKILL.md. Its commands use npm, which can reach the network depending on how they are called.
From URLs in SKILL.md, links to its own repository left out.
Names no API keys, tokens, secrets or passwords.
From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
R3f Animation loads about 5.5k tokens when it runs. Until then it costs about 54 tokens; SKILL.md has 222 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check found no risky patterns in SKILL.md.
Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.
The full file from zebbern/claude-code-guide at commit 9cde898, republished under its MIT licence (© zebbern). 222 words, ~5,500 tokens.
.claude/skills/r3f-animation/SKILL.md (or your agent's skills folder).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>
)
}The core animation hook in R3F. Runs every frame.
import { useFrame } from '@react-three/fiber'
import { useRef } from 'react'
function AnimatedMesh() {
const meshRef = useRef()
useFrame((state, delta) => {
// state contains: clock, camera, scene, gl, mouse, etc.
// delta is time since last frame in seconds
meshRef.current.rotation.y += delta
})
return (
<mesh ref={meshRef}>
<boxGeometry />
<meshStandardMaterial color="orange" />
</mesh>
)
}useFrame((state, delta, xrFrame) => {
const {
clock, // THREE.Clock
camera, // Current camera
scene, // Scene
gl, // WebGLRenderer
mouse, // Normalized mouse position (-1 to 1)
pointer, // Same as mouse
viewport, // Viewport dimensions
size, // Canvas size
raycaster, // Raycaster
get, // Get current state
set, // Set state
invalidate, // Request re-render (when frameloop="demand")
} = state
// Time-based animation
const t = clock.getElapsedTime()
meshRef.current.position.y = Math.sin(t) * 2
})// Lower numbers run first. Default is 0.
// Use negative for pre-render, positive for post-render
function PreRender() {
useFrame(() => {
// Runs before main render
}, -1)
}
function PostRender() {
useFrame(() => {
// Runs after main render
}, 1)
}
function DefaultRender() {
useFrame(() => {
// Runs at default priority (0)
})
}function ConditionalAnimation({ isAnimating }) {
const meshRef = useRef()
useFrame((state, delta) => {
if (!isAnimating) return
meshRef.current.rotation.y += delta
})
return <mesh ref={meshRef}>...</mesh>
}The recommended way to play animations from GLTF/GLB files.
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(() => {
// Play first animation
actions[names[0]]?.play()
}, [actions, names])
return <primitive ref={group} object={scene} />
}function Character() {
const group = useRef()
const { scene, animations } = useGLTF('/models/character.glb')
const { actions, mixer } = useAnimations(animations, group)
useEffect(() => {
const action = actions['Walk']
if (action) {
// Playback control
action.play()
action.stop()
action.reset()
action.paused = true
// Speed
action.timeScale = 1.5 // 1.5x speed
action.timeScale = -1 // Reverse
// Loop modes
action.loop = THREE.LoopOnce
action.loop = THREE.LoopRepeat
action.loop = THREE.LoopPingPong
action.repetitions = 3
action.clampWhenFinished = true
// Weight (for blending)
action.weight = 1
}
}, [actions])
return <primitive ref={group} object={scene} />
}import { useGLTF, useAnimations } from '@react-three/drei'
import { useState, useEffect, useRef } from 'react'
function Character() {
const group = useRef()
const { scene, animations } = useGLTF('/models/character.glb')
const { actions } = useAnimations(animations, group)
const [currentAnim, setCurrentAnim] = useState('Idle')
useEffect(() => {
// Fade out all animations
Object.values(actions).forEach(action => {
action?.fadeOut(0.5)
})
// Fade in current animation
actions[currentAnim]?.reset().fadeIn(0.5).play()
}, [currentAnim, actions])
return (
<group ref={group}>
<primitive object={scene} />
</group>
)
}function AnimatedModel() {
const group = useRef()
const { scene, animations } = useGLTF('/models/character.glb')
const { actions, mixer } = useAnimations(animations, group)
useEffect(() => {
// Listen for animation events
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])
return <primitive ref={group} object={scene} />
}function CharacterController({ speed = 0 }) {
const group = useRef()
const { scene, animations } = useGLTF('/models/character.glb')
const { actions } = useAnimations(animations, group)
useEffect(() => {
// Start all animations
actions['Idle']?.play()
actions['Walk']?.play()
actions['Run']?.play()
}, [actions])
// Blend based on speed
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)
}
})
return <primitive ref={group} object={scene} />
}Physics-based spring animations that integrate with R3F.
npm install @react-spring/threeimport { 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>
)
}import { useSpring, animated, config } from '@react-spring/three'
function SpringPresets() {
const { position } = useSpring({
position: [0, 2, 0],
config: config.wobbly // Presets: default, gentle, wobbly, stiff, slow, molasses
})
// Or custom config
const { rotation } = useSpring({
rotation: [0, Math.PI, 0],
config: {
mass: 1,
tension: 170,
friction: 26,
clamp: false,
precision: 0.01,
velocity: 0,
}
})
return (
<animated.mesh position={position} rotation={rotation}>
<boxGeometry />
<meshStandardMaterial />
</animated.mesh>
)
}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) => {
if (i === index) return { scale: 1.5 }
return { 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>
))
}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>
)
}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), then rotation (0.5-1)
useChain([scaleRef, rotationRef], [0, 0.5])
return (
<animated.mesh scale={scale} rotation={rotation}>
<boxGeometry />
<meshStandardMaterial color="cyan" />
</animated.mesh>
)
}Blend between different mesh shapes.
import { useGLTF } from '@react-three/drei'
import { useFrame } from '@react-three/fiber'
import { useRef } from 'react'
function MorphingFace() {
const { scene, nodes } = useGLTF('/models/face.glb')
const meshRef = useRef()
useFrame(({ clock }) => {
const t = clock.getElapsedTime()
// Access morph target influences
if (meshRef.current?.morphTargetInfluences) {
// Animate smile
const smileIndex = meshRef.current.morphTargetDictionary['smile']
meshRef.current.morphTargetInfluences[smileIndex] = (Math.sin(t) + 1) / 2
}
})
return (
<primitive ref={meshRef} object={nodes.Face} />
)
}function MorphControls({ morphInfluences }) {
const { nodes } = useGLTF('/models/face.glb')
const meshRef = useRef()
useFrame(() => {
if (meshRef.current?.morphTargetInfluences) {
Object.entries(morphInfluences).forEach(([name, value]) => {
const index = meshRef.current.morphTargetDictionary[name]
if (index !== undefined) {
meshRef.current.morphTargetInfluences[index] = value
}
})
}
})
return <primitive ref={meshRef} object={nodes.Face} />
}
// Usage
<MorphControls morphInfluences={{ smile: 0.5, blink: 1, angry: 0 }} />import { useGLTF } from '@react-three/drei'
import { useFrame } from '@react-three/fiber'
import { useEffect, useRef } from 'react'
function SkeletalCharacter() {
const { scene } = useGLTF('/models/character.glb')
const headBoneRef = useRef()
useEffect(() => {
// Find skeleton
scene.traverse((child) => {
if (child.isSkinnedMesh) {
const skeleton = child.skeleton
const headBone = skeleton.bones.find(b => b.name === 'Head')
headBoneRef.current = headBone
}
})
}, [scene])
// Animate bone
useFrame(({ clock }) => {
if (headBoneRef.current) {
headBoneRef.current.rotation.y = Math.sin(clock.elapsedTime) * 0.3
}
})
return <primitive object={scene} />
}function CharacterWithWeapon() {
const { scene } = useGLTF('/models/character.glb')
const weaponRef = useRef()
const handBoneRef = useRef()
useEffect(() => {
scene.traverse((child) => {
if (child.isSkinnedMesh) {
const handBone = child.skeleton.bones.find(b => b.name === 'RightHand')
if (handBone && weaponRef.current) {
handBone.add(weaponRef.current)
handBoneRef.current = handBone
}
}
})
return () => {
// Cleanup
if (handBoneRef.current && weaponRef.current) {
handBoneRef.current.remove(weaponRef.current)
}
}
}, [scene])
return (
<>
<primitive object={scene} />
<mesh ref={weaponRef} position={[0, 0, 0.5]}>
<boxGeometry args={[0.1, 0.1, 1]} />
<meshStandardMaterial color="gray" />
</mesh>
</>
)
}import { useFrame } from '@react-three/fiber'
import { useRef } from 'react'
import * as THREE from 'three'
function SmoothFollow({ target }) {
const meshRef = useRef()
const currentPos = useRef(new THREE.Vector3())
useFrame((state, delta) => {
// Lerp towards target
currentPos.current.lerp(target, delta * 5)
meshRef.current.position.copy(currentPos.current)
})
return (
<mesh ref={meshRef}>
<sphereGeometry args={[0.5]} />
<meshStandardMaterial color="blue" />
</mesh>
)
}function SpringMesh({ target = 0 }) {
const meshRef = useRef()
const spring = useRef({
position: 0,
velocity: 0,
stiffness: 100,
damping: 10
})
useFrame((state, delta) => {
const s = spring.current
const force = -s.stiffness * (s.position - target)
const dampingForce = -s.damping * s.velocity
s.velocity += (force + dampingForce) * delta
s.position += s.velocity * delta
meshRef.current.position.y = s.position
})
return (
<mesh ref={meshRef}>
<boxGeometry />
<meshStandardMaterial color="green" />
</mesh>
)
}function OscillatingMesh() {
const meshRef = useRef()
useFrame(({ clock }) => {
const t = clock.elapsedTime
// Sine wave
meshRef.current.position.y = Math.sin(t * 2) * 0.5
// Circular motion
meshRef.current.position.x = Math.cos(t) * 2
meshRef.current.position.z = Math.sin(t) * 2
// Bouncing
meshRef.current.position.y = Math.abs(Math.sin(t * 3)) * 2
// Figure 8
meshRef.current.position.x = Math.sin(t) * 2
meshRef.current.position.z = Math.sin(t * 2) * 1
})
return (
<mesh ref={meshRef}>
<sphereGeometry args={[0.3]} />
<meshStandardMaterial color="purple" />
</mesh>
)
}import { Float } from '@react-three/drei'
function FloatingObject() {
return (
<Float
speed={1} // Animation speed
rotationIntensity={1} // Rotation intensity
floatIntensity={1} // Float intensity
floatingRange={[-0.1, 0.1]} // Range of y-axis float
>
<mesh>
<boxGeometry />
<meshStandardMaterial color="gold" />
</mesh>
</Float>
)
}import { MeshWobbleMaterial, MeshDistortMaterial } from '@react-three/drei'
function WobblyMesh() {
return (
<mesh>
<torusKnotGeometry args={[1, 0.4, 100, 16]} />
<MeshWobbleMaterial
factor={1} // Wobble amplitude
speed={2} // Wobble speed
color="hotpink"
/>
</mesh>
)
}
function DistortedMesh() {
return (
<mesh>
<sphereGeometry args={[1, 64, 64]} />
<MeshDistortMaterial
distort={0.5} // Distortion amount
speed={2} // Animation speed
color="cyan"
/>
</mesh>
)
}import { Trail } from '@react-three/drei'
import { useFrame } from '@react-three/fiber'
import { useRef } from 'react'
function TrailingMesh() {
const meshRef = useRef()
useFrame(({ clock }) => {
const t = clock.elapsedTime
meshRef.current.position.x = Math.sin(t) * 3
meshRef.current.position.y = Math.cos(t * 2) * 2
})
return (
<Trail
width={2}
length={8}
color="hotpink"
attenuation={(t) => t * t}
>
<mesh ref={meshRef}>
<sphereGeometry args={[0.2]} />
<meshStandardMaterial color="white" />
</mesh>
</Trail>
)
}import { create } from 'zustand'
import { useFrame } from '@react-three/fiber'
const useStore = create((set) => ({
isAnimating: false,
speed: 1,
toggleAnimation: () => set((state) => ({ isAnimating: !state.isAnimating })),
setSpeed: (speed) => set({ speed })
}))
function AnimatedMesh() {
const meshRef = useRef()
const { isAnimating, speed } = useStore()
useFrame((state, delta) => {
if (isAnimating) {
meshRef.current.rotation.y += delta * speed
}
})
return (
<mesh ref={meshRef}>
<boxGeometry />
<meshStandardMaterial color="orange" />
</mesh>
)
}
// UI Component
function Controls() {
const { toggleAnimation, setSpeed } = useStore()
return (
<div>
<button onClick={toggleAnimation}>Toggle</button>
<input
type="range"
min="0"
max="5"
step="0.1"
onChange={(e) => setSpeed(parseFloat(e.target.value))}
/>
</div>
)
}Critical patterns for high-performance state management in animations.
Use getState() instead of hooks inside useFrame for zero subscription overhead:
import { create } from 'zustand'
const useGameStore = create((set) => ({
playerPosition: [0, 0, 0],
targetPosition: [0, 0, 0],
setPlayerPosition: (pos) => set({ playerPosition: pos }),
}))
function Player() {
const meshRef = useRef()
useFrame((state, delta) => {
// ✅ GOOD: getState() has no subscription overhead
const { targetPosition } = useGameStore.getState()
// Lerp towards target
meshRef.current.position.lerp(
new THREE.Vector3(...targetPosition),
delta * 5
)
})
return (
<mesh ref={meshRef}>
<boxGeometry />
<meshStandardMaterial color="blue" />
</mesh>
)
}Subscribe to state changes without triggering React re-renders:
import { useEffect, useRef } from 'react'
function Enemy() {
const meshRef = useRef()
useEffect(() => {
// Subscribe directly - updates mesh without re-rendering component
const unsub = useGameStore.subscribe(
(state) => state.playerPosition,
(playerPos) => {
// Look at player (runs on every state change, no re-render)
meshRef.current.lookAt(...playerPos)
}
)
return unsub
}, [])
return (
<mesh ref={meshRef}>
<coneGeometry args={[0.5, 1, 4]} />
<meshStandardMaterial color="red" />
</mesh>
)
}Subscribe to multiple values efficiently:
import { shallow } from 'zustand/shallow'
function HUD() {
// Only re-renders when health OR score actually changes
const { health, score } = useGameStore(
(state) => ({ health: state.health, score: state.score }),
shallow
)
return (
<Html>
<div>Health: {health}</div>
<div>Score: {score}</div>
</Html>
)
}
// For single values, no shallow needed
const health = useGameStore((state) => state.health)Separate state-dependent UI from animated 3D objects:
// ❌ BAD: Parent re-renders cause animation jank
function BadPattern() {
const [score, setScore] = useState(0)
const meshRef = useRef()
useFrame((_, delta) => {
meshRef.current.rotation.y += delta // Affected by score re-renders
})
return (
<>
<mesh ref={meshRef}>...</mesh>
<ScoreDisplay score={score} />
</>
)
}
// ✅ GOOD: Isolated animation component
function GoodPattern() {
return (
<>
<AnimatedMesh /> {/* Never re-renders from score */}
<ScoreDisplay /> {/* Has its own state subscription */}
</>
)
}
function AnimatedMesh() {
const meshRef = useRef()
useFrame((_, delta) => {
meshRef.current.rotation.y += delta // Smooth, uninterrupted
})
return <mesh ref={meshRef}>...</mesh>
}
function ScoreDisplay() {
const score = useGameStore((state) => state.score)
return <Html><div>Score: {score}</div></Html>
}// Isolate animation to prevent parent re-renders
function Scene() {
return (
<>
<StaticMesh /> {/* Never re-renders */}
<AnimatedMesh /> {/* Only this updates */}
</>
)
}
// Throttle expensive operations
function ThrottledAnimation() {
const meshRef = useRef()
const accumulated = useRef(0)
useFrame((state, delta) => {
accumulated.current += delta
// Only update every 100ms
if (accumulated.current > 0.1) {
// Expensive calculation here
accumulated.current = 0
}
// Cheap operations every frame
meshRef.current.rotation.y += delta
})
}r3f-loaders - Loading animated GLTF modelsr3f-fundamentals - useFrame and animation loopr3f-shaders - Vertex animation in shaders© zebbern, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
Just SKILL.md in skills/r3f-animation of zebbern/claude-code-guide.
Open the folder on GitHubat commit 9cde898
R3f Animation next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| R3f Animation this skillzebbern/claude-code-guide | 4.7k | — | ~5.5k | Automated safety check: Pass | MIT | |
| Image to Three.js Modelimg2threejs/img2threejs | 18k | 1 repos | ~8.2k | Automated safety check: Pass | Apache-2.0 | |
| Web CloneJane-xiaoer/claude-skill-web-clone | 1k | 1 repos | ~2.7k | Automated safety check: Pass | MIT | |
| Threejs Game Directormajidmanzarpour/threejs-game-skills | 2.5k | — | ~2.2k | Automated safety check: Pass | MIT | |
| Game Asset Generatorhtdt/godogen | 7.1k | — | ~2.8k | Automated safety check: Pass | MIT | |
| Threejs Gameplay Systemsvalkor-ai/loom | 1.2k | 1 repos | ~1.4k | Automated safety check: Pass | Apache-2.0 |
img2threejs/img2threejs
Rebuilds the object in a reference image as a procedural, animation-ready Three.js model written entirely in code, using staged sculpting with quality checks.
Jane-xiaoer/claude-skill-web-clone
网站复刻 / 克隆方法论。USE WHEN 用户说 复刻网站、克隆网站、clone website、抄个站、仿站、 照着这个站做一个、reproduce site、还原某个网页效果、把这个站搬下来改成我的、 复刻某个交互/WebGL/Canvas/Three.js 效果。提供「先拿真源码 → 判路径 → 逆向拆解 → 搭工程 → 替换内容」的可移植决策树,覆盖静态站 /…
majidmanzarpour/threejs-game-skills
Entrypoint for building, upgrading, and finishing Three.js browser games.
htdt/godogen
Generates game art from text prompts: PNG images, GLB 3D models, rigged characters, animations and sprites, with background removal.
valkor-ai/loom
Build and iterate playable Three.js game systems: starter scaffold, architecture, design briefs, core loops, level and encounter design, entities, input, camera, collision and physics, scoring…
calesthio/OpenMontage
Build deterministic, editable, free-viewpoint Three.js worlds from text or structured briefs.
zebbern/claude-code-guide
This skill should be used when setting up, auditing, or enforcing internationalization/localization in UI codebases (React/TS, i18next or similar, JSON locales), including installing/configuring the…
zebbern/claude-code-guide
Interactive system flow tracing across CODE, API, AUTH, DATA, NETWORK layers with SQLite persistence and Mermaid export.
zebbern/claude-code-guide
Generate publication-quality PNG chart images from data, supporting line, bar, area, candlestick, pie, and heatmap charts.
zebbern/claude-code-guide
Analyze codebases and automatically generate architecture diagrams, flowcharts, and org charts.
zebbern/claude-code-guide
Scan code for security issues: dependency vulnerabilities (npm/pip audit), secret leaks (regex and entropy analysis), and OWASP anti-patterns like SQL injection, XSS, or command injection.
zebbern/claude-code-guide
This skill should be used when the user asks to "test for insecure direct object references," "find IDOR vulnerabilities," "exploit broken access control," "enumerate user IDs or object references,"…
Categories
React Three Fiber animation - useFrame, useAnimations, spring physics, keyframes. R3f Animation is an agent skill from zebbern/claude-code-guide. React Three Fiber animation - useFrame, useAnimations, spring physics, keyframes.
R3f Animation fits situations like: animating objects; playing GLTF animations; creating procedural motion; implementing physics-based movement.
Run `npx skills add zebbern/claude-code-guide --skill r3f-animation -a claude-code`. Or copy the skill folder (skills/r3f-animation in zebbern/claude-code-guide) into .claude/skills/r3f-animation in your project. Claude Code loads it when a task matches its description.
Run `npx skills add zebbern/claude-code-guide --skill r3f-animation -a codex`. Or copy the skill folder (skills/r3f-animation in zebbern/claude-code-guide) into .agents/skills/r3f-animation in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add zebbern/claude-code-guide --skill r3f-animation -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/r3f-animation, .gemini/skills/r3f-animation, .github/skills/r3f-animation and .opencode/skills/r3f-animation in your project.
Going by SKILL.md and its folder, R3f Animation needs the command-line tools its instructions call (npm). Our summary lists: Node.js.
SKILL.md contains no URLs. Its commands use npm, which can reach the network depending on how they are called. This is read from the text; nothing was executed.
Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.
R3f Animation is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.
About 5.5k tokens (SKILL.md is roughly 22k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.
Skills that share tags, products or a category with R3f Animation: Image to Three.js Model (img2threejs/img2threejs, 18k stars), Web Clone (Jane-xiaoer/claude-skill-web-clone, 1k stars), Threejs Game Director (majidmanzarpour/threejs-game-skills, 2.5k stars) and Game Asset Generator (htdt/godogen, 7.1k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
zebbern (a GitHub user) maintains it in zebbern/claude-code-guide, which has 4,652 GitHub stars. The repository holds 46 skills in this directory. The repository was last updated on October 9, 2026.
Source: zebbern/claude-code-guide on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.