r3f-animation skill
React Three Fiber animation - useFrame, useAnimations, spring physics, keyframes. Use when animating objects, playing GLTF animations, creating procedural motion, or implementing physics-based movement.
Is the r3f-animation skill safe?
Clean: nothing in its files matched our rules. We read 1 file in the folder on 2026-09-28.
No findings.
Install the r3f-animation skill
A skill is a folder. Copy it into your agent's skills folder and the agent loads it when the task matches its description.
git clone --depth 1 https://github.com/zebbern/claude-code-guide.git /tmp/claude-code-guide mkdir -p ~/.claude/skills cp -r /tmp/claude-code-guide/skills/r3f-animation ~/.claude/skills/r3f-animation
In the Claude apps, zip the folder and upload it from the Skills settings. The folder on GitHub
The instructions your agent would load
SKILL.md as published, without the frontmatter. Read it on GitHub
React Three Fiber Animation
Quick Start
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 Hook
The core animation hook in R3F. Runs every frame.
Basic Usage
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>
)
}State Object
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
})Render Priority
// 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)
})
}Conditional Animation
function ConditionalAnimation({ isAnimating }) {
const meshRef = useRef()
useFrame((state, delta) => {
if (!isAnimating) return
meshRef.current.rotation.y += delta
})
return <mesh ref={meshRef}>...</mesh>
}GLTF Animations with useAnimations
The recommended way to play animations from GLTF/GLB files.
Basic Usage
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} />
}Animation Control
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} />
}Crossfade Between Animations
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>
)
}Animation Events
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} />
}Animation Blending
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} />
}Spring Animation (@react-spring/three)
Physics-based spring animations that integrate with R3F.
Installation
npm install @react-spring/threeBasic Spring
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>
)
}Spring Config Presets
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>
)
}Multiple Springs
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>
))
}Gesture Integration
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>
)
}Chain Animations
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>
)
}Morph Targets
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} />
)
}Controlled Morph Targets
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 }} />Skeletal Animation
Accessing Bones
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} />
}Bone Attachments
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>
</>
)
}Procedural Animation Patterns
Smooth Damping
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>
)
}Spring Physics (Manual)
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>
)
}Oscillation Patterns
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>
)
}Drei Animation Helpers
Float
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>
)
}MeshWobbleMaterial / MeshDistortMaterial
More skills from zebbern/claude-code-guide
- Aacademic-paper-reviewerSimulates academic peer review, evaluating papers across Originality, Methodology, Results, and Writing to provide Major/Minor Revision recommendations with actionable feedback. Triggers when a user asks to \"review my paper,\" \"simulate peer review,\" or \"give my paper a peer review.
- Aactive-directory-attacksThis skill should be used when the user asks to "attack Active Directory", "exploit AD", "Kerberoasting", "DCSync", "pass-the-hash", "BloodHound enumeration", "Golden Ticket", "Silver Ticket", "AS-REP roasting", "NTLM relay", or needs guidance on Windows domain penetration testing.
- Capi-fuzzing-bug-bountyThis skill should be used when the user asks to "test API security", "fuzz APIs", "find IDOR vulnerabilities", "test REST API", "test GraphQL", "API penetration testing", "bug bounty API testing", or needs guidance on API security assessment techniques.
- Aapi-shape-explorerGenerate multiple radically different interface designs for a module using parallel sub-agents. Use when user wants to design an API, explore interface options, compare module shapes, or mentions "design it twice".
- Aaudit-flowInteractive system flow tracing across CODE, API, AUTH, DATA, NETWORK layers with SQLite persistence and Mermaid export. Use for security audits, compliance documentation, flow tracing, feature ideation, brainstorming, debugging, architecture reviews, or incident post-mortems. Triggers on audit, trace flow, document flow, security review, debug flow, brainstorm, architecture review, post-mortem, incident review.
- Aauthentication-patternsAuthentication patterns: session vs JWT vs OAuth comparison, provider selection (NextAuth, Clerk, Supabase Auth), security checklist, and common mistakes. Use when implementing auth, reviewing auth flows, or choosing auth providers.
- Aaws-penetration-testingThis skill should be used when the user asks to "pentest AWS", "test AWS security", "enumerate IAM", "exploit cloud infrastructure", "AWS privilege escalation", "S3 bucket testing", "metadata SSRF", "Lambda exploitation", or needs guidance on Amazon Web Services security assessment.
- Abroken-authenticationThis skill should be used when the user asks to "test for broken authentication vulnerabilities", "assess session management security", "perform credential stuffing tests", "evaluate password policies", "test for session fixation", or "identify authentication bypass flaws". It provides comprehensive techniques for identifying authentication and session management weaknesses in web applications.
- Cburp-suite-testingThis skill should be used when the user asks to "intercept HTTP traffic", "modify web requests", "use Burp Suite for testing", "perform web vulnerability scanning", "test with Burp Repeater", "analyze HTTP history", or "configure proxy for web testing". It provides comprehensive guidance for using Burp Suite's core features for web application security testing.
- AcachingCaching strategies — invalidation, TTL guidelines, cache keys, cache layers, and when not to cache. Use when implementing or reviewing caching logic.
- Achart-imageGenerate publication-quality PNG chart images from data, supporting line, bar, area, candlestick, pie, and heatmap charts. Triggers when the user asks to visualize data, create a graph, plot a time series, or generate a chart for a report, alert, or dashboard. Runs as a lightweight, headless Node.js process without a browser.
- Dcloud-penetration-testingThis skill should be used when the user asks to "perform cloud penetration testing", "assess Azure or AWS or GCP security", "enumerate cloud resources", "exploit cloud misconfigurations", "test O365 security", "extract secrets from cloud environments", or "audit cloud infrastructure". It provides comprehensive techniques for security assessment across major cloud platforms.