Loading...
Loading...
Build declarative 3D scenes with React Three Fiber (R3F) - a React renderer for Three.js. Use when building interactive 3D experiences in React applications with component-based architecture, state management, and reusable abstractions. Ideal for product configurators, portfolios, games, data visualization, and immersive web experiences.
npx skill4agent add freshtechbro/claudedesignskills react-three-fiber<Canvas>import { Canvas } from '@react-three/fiber'
function App() {
return (
<Canvas
camera={{ position: [0, 0, 5], fov: 75 }}
gl={{ antialias: true }}
dpr={[1, 2]}
>
{/* 3D content goes here */}
</Canvas>
)
}cameragldprshadowsframeloopflatlinear// THREE.Mesh + THREE.BoxGeometry + THREE.MeshStandardMaterial
<mesh position={[0, 0, 0]} rotation={[0, Math.PI / 4, 0]}>
<boxGeometry args={[1, 1, 1]} />
<meshStandardMaterial color="hotpink" />
</mesh>positionobject.position.set(x, y, z)rotationobject.rotation.set(x, y, z)scaleobject.scale.set(x, y, z)argsattachattach="material"// Full notation
<mesh position={[1, 2, 3]} />
// Axis-specific (dash notation)
<mesh position-x={1} position-y={2} position-z={3} />import { useFrame } from '@react-three/fiber'
import { useRef } from 'react'
function RotatingBox() {
const meshRef = useRef()
useFrame((state, delta) => {
// Rotate mesh on every frame
meshRef.current.rotation.x += delta
meshRef.current.rotation.y += delta * 0.5
// Access scene state
const time = state.clock.elapsedTime
meshRef.current.position.y = Math.sin(time) * 2
})
return (
<mesh ref={meshRef}>
<boxGeometry />
<meshStandardMaterial color="orange" />
</mesh>
)
}statedeltaxrFramesetStateuseFrameimport { useThree } from '@react-three/fiber'
function CameraInfo() {
const { camera, gl, scene, size, viewport } = useThree()
// Selective subscription (only re-render on size change)
const size = useThree((state) => state.size)
// Get state non-reactively
const get = useThree((state) => state.get)
const freshState = get() // Latest state without triggering re-render
return null
}camerasceneglsizeviewportclockpointerinvalidate()setSize()import { Suspense } from 'react'
import { useLoader } from '@react-three/fiber'
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'
import { TextureLoader } from 'three'
function Model() {
const gltf = useLoader(GLTFLoader, '/model.glb')
return <primitive object={gltf.scene} />
}
function TexturedMesh() {
const texture = useLoader(TextureLoader, '/texture.jpg')
return (
<mesh>
<boxGeometry />
<meshStandardMaterial map={texture} />
</mesh>
)
}
function App() {
return (
<Canvas>
<Suspense fallback={<LoadingIndicator />}>
<Model />
<TexturedMesh />
</Suspense>
</Canvas>
)
}const [texture1, texture2, texture3] = useLoader(TextureLoader, [
'/tex1.jpg',
'/tex2.jpg',
'/tex3.jpg'
])import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader'
useLoader(GLTFLoader, '/model.glb', (loader) => {
const dracoLoader = new DRACOLoader()
dracoLoader.setDecoderPath('/draco/')
loader.setDRACOLoader(dracoLoader)
})// Pre-load assets before component mounts
useLoader.preload(GLTFLoader, '/model.glb')import { Canvas } from '@react-three/fiber'
function Scene() {
return (
<>
{/* Lights */}
<ambientLight intensity={0.5} />
<spotLight position={[10, 10, 10]} angle={0.15} penumbra={1} />
{/* Objects */}
<mesh position={[0, 0, 0]}>
<boxGeometry args={[1, 1, 1]} />
<meshStandardMaterial color="hotpink" />
</mesh>
</>
)
}
function App() {
return (
<Canvas camera={{ position: [0, 0, 5], fov: 75 }}>
<Scene />
</Canvas>
)
}import { useState } from 'react'
function InteractiveBox() {
const [hovered, setHovered] = useState(false)
const [active, setActive] = useState(false)
return (
<mesh
scale={active ? 1.5 : 1}
onClick={() => setActive(!active)}
onPointerOver={() => setHovered(true)}
onPointerOut={() => setHovered(false)}
>
<boxGeometry />
<meshStandardMaterial color={hovered ? 'hotpink' : 'orange'} />
</mesh>
)
}import { useRef } from 'react'
import { useFrame } from '@react-three/fiber'
function AnimatedSphere() {
const meshRef = useRef()
useFrame((state, delta) => {
// Rotate
meshRef.current.rotation.y += delta
// Oscillate position
const time = state.clock.elapsedTime
meshRef.current.position.y = Math.sin(time) * 2
})
return (
<mesh ref={meshRef}>
<sphereGeometry args={[1, 32, 32]} />
<meshStandardMaterial color="cyan" />
</mesh>
)
}import { Suspense } from 'react'
import { useLoader } from '@react-three/fiber'
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'
function Model({ url }) {
const gltf = useLoader(GLTFLoader, url)
return (
<primitive
object={gltf.scene}
scale={0.5}
position={[0, 0, 0]}
/>
)
}
function App() {
return (
<Canvas>
<Suspense fallback={<LoadingPlaceholder />}>
<Model url="/model.glb" />
</Suspense>
</Canvas>
)
}
function LoadingPlaceholder() {
return (
<mesh>
<boxGeometry />
<meshBasicMaterial wireframe />
</mesh>
)
}function Lighting() {
return (
<>
{/* Ambient light for base illumination */}
<ambientLight intensity={0.3} />
{/* Directional light with shadows */}
<directionalLight
position={[5, 5, 5]}
intensity={1}
castShadow
shadow-mapSize-width={2048}
shadow-mapSize-height={2048}
/>
{/* Point light for accent */}
<pointLight position={[-5, 5, -5]} intensity={0.5} color="blue" />
{/* Spot light for focused illumination */}
<spotLight
position={[10, 10, 10]}
angle={0.3}
penumbra={1}
intensity={1}
/>
</>
)
}import { useMemo, useRef } from 'react'
import * as THREE from 'three'
import { useFrame } from '@react-three/fiber'
function Particles({ count = 1000 }) {
const meshRef = useRef()
// Generate random positions
const particles = useMemo(() => {
const temp = []
for (let i = 0; i < count; i++) {
const t = Math.random() * 100
const factor = 20 + Math.random() * 100
const speed = 0.01 + Math.random() / 200
const x = Math.random() * 2 - 1
const y = Math.random() * 2 - 1
const z = Math.random() * 2 - 1
temp.push({ t, factor, speed, x, y, z, mx: 0, my: 0 })
}
return temp
}, [count])
const dummy = useMemo(() => new THREE.Object3D(), [])
useFrame(() => {
particles.forEach((particle, i) => {
let { t, factor, speed, x, y, z } = particle
t = particle.t += speed / 2
const a = Math.cos(t) + Math.sin(t * 1) / 10
const b = Math.sin(t) + Math.cos(t * 2) / 10
const s = Math.cos(t)
dummy.position.set(
x + Math.cos((t / 10) * factor) + (Math.sin(t * 1) * factor) / 10,
y + Math.sin((t / 10) * factor) + (Math.cos(t * 2) * factor) / 10,
z + Math.cos((t / 10) * factor) + (Math.sin(t * 3) * factor) / 10
)
dummy.scale.set(s, s, s)
dummy.updateMatrix()
meshRef.current.setMatrixAt(i, dummy.matrix)
})
meshRef.current.instanceMatrix.needsUpdate = true
})
return (
<instancedMesh ref={meshRef} args={[null, null, count]}>
<sphereGeometry args={[0.05, 8, 8]} />
<meshBasicMaterial color="white" />
</instancedMesh>
)
}function Robot() {
return (
<group position={[0, 0, 0]}>
{/* Body */}
<mesh position={[0, 0, 0]}>
<boxGeometry args={[1, 2, 1]} />
<meshStandardMaterial color="gray" />
</mesh>
{/* Head */}
<mesh position={[0, 1.5, 0]}>
<sphereGeometry args={[0.5, 32, 32]} />
<meshStandardMaterial color="silver" />
</mesh>
{/* Arms */}
<group position={[-0.75, 0.5, 0]}>
<mesh>
<cylinderGeometry args={[0.1, 0.1, 1.5]} />
<meshStandardMaterial color="darkgray" />
</mesh>
</group>
<group position={[0.75, 0.5, 0]}>
<mesh>
<cylinderGeometry args={[0.1, 0.1, 1.5]} />
<meshStandardMaterial color="darkgray" />
</mesh>
</group>
</group>
)
}import { OrbitControls } from '@react-three/drei'
<Canvas>
<OrbitControls
makeDefault
enableDamping
dampingFactor={0.05}
minDistance={3}
maxDistance={20}
/>
<Box />
</Canvas>import { Environment, ContactShadows } from '@react-three/drei'
<Canvas>
{/* HDRI environment map */}
<Environment preset="sunset" />
{/* Or custom */}
<Environment files="/hdri.hdr" />
{/* Soft contact shadows */}
<ContactShadows
opacity={0.5}
scale={10}
blur={1}
far={10}
resolution={256}
/>
<Model />
</Canvas>import { Text, Text3D } from '@react-three/drei'
// 2D Billboard text
<Text
position={[0, 2, 0]}
fontSize={1}
color="white"
anchorX="center"
anchorY="middle"
>
Hello World
</Text>
// 3D extruded text
<Text3D
font="/fonts/helvetiker_regular.typeface.json"
size={1}
height={0.2}
>
3D Text
<meshNormalMaterial />
</Text3D>import { useGLTF } from '@react-three/drei'
function Model() {
const { scene, materials, nodes } = useGLTF('/model.glb')
return <primitive object={scene} />
}
// Pre-load
useGLTF.preload('/model.glb')import { Center, Bounds, useBounds } from '@react-three/drei'
// Auto-center objects
<Center>
<Model />
</Center>
// Auto-fit camera to bounds
<Bounds fit clip observe margin={1.2}>
<Model />
</Bounds>import { Html } from '@react-three/drei'
<mesh>
<boxGeometry />
<meshStandardMaterial />
<Html
position={[0, 1, 0]}
center
distanceFactor={10}
>
<div className="annotation">
This is a box
</div>
</Html>
</mesh>import { ScrollControls, Scroll, useScroll } from '@react-three/drei'
import { useFrame } from '@react-three/fiber'
function AnimatedScene() {
const scroll = useScroll()
const meshRef = useRef()
useFrame(() => {
const offset = scroll.offset // 0-1 normalized scroll position
meshRef.current.position.y = offset * 10
})
return <mesh ref={meshRef}>...</mesh>
}
<Canvas>
<ScrollControls pages={3} damping={0.5}>
<Scroll>
<AnimatedScene />
</Scroll>
{/* HTML overlay */}
<Scroll html>
<div style={{ height: '100vh' }}>
<h1>Scrollable content</h1>
</div>
</Scroll>
</ScrollControls>
</Canvas>import { useRef, useEffect } from 'react'
import { useFrame } from '@react-three/fiber'
import gsap from 'gsap'
function AnimatedBox() {
const meshRef = useRef()
useEffect(() => {
// GSAP timeline animation
const tl = gsap.timeline({ repeat: -1, yoyo: true })
tl.to(meshRef.current.position, {
y: 2,
duration: 1,
ease: 'power2.inOut'
})
.to(meshRef.current.rotation, {
y: Math.PI * 2,
duration: 2,
ease: 'none'
}, 0)
return () => tl.kill()
}, [])
return (
<mesh ref={meshRef}>
<boxGeometry />
<meshStandardMaterial color="orange" />
</mesh>
)
}import { motion } from 'framer-motion-3d'
function AnimatedSphere() {
return (
<motion.mesh
initial={{ scale: 0 }}
animate={{ scale: 1 }}
transition={{ duration: 1 }}
>
<sphereGeometry />
<meshStandardMaterial color="hotpink" />
</motion.mesh>
)
}import create from 'zustand'
const useStore = create((set) => ({
color: 'orange',
setColor: (color) => set({ color })
}))
function Box() {
const color = useStore((state) => state.color)
const setColor = useStore((state) => state.setColor)
return (
<mesh onClick={() => setColor('hotpink')}>
<boxGeometry />
<meshStandardMaterial color={color} />
</mesh>
)
}<Canvas frameloop="demand">
{/* Only renders when needed */}
</Canvas>
// Manually trigger render
function MyComponent() {
const invalidate = useThree((state) => state.invalidate)
return (
<mesh onClick={() => invalidate()}>
<boxGeometry />
<meshStandardMaterial />
</mesh>
)
}<instancedMesh>function Particles({ count = 10000 }) {
const meshRef = useRef()
useEffect(() => {
const temp = new THREE.Object3D()
for (let i = 0; i < count; i++) {
temp.position.set(
Math.random() * 10 - 5,
Math.random() * 10 - 5,
Math.random() * 10 - 5
)
temp.updateMatrix()
meshRef.current.setMatrixAt(i, temp.matrix)
}
meshRef.current.instanceMatrix.needsUpdate = true
}, [count])
return (
<instancedMesh ref={meshRef} args={[null, null, count]}>
<sphereGeometry args={[0.1, 8, 8]} />
<meshBasicMaterial color="white" />
</instancedMesh>
)
}// Disable for always-visible objects
<mesh frustumCulled={false}>
<boxGeometry />
<meshStandardMaterial />
</mesh>import { Detailed } from '@react-three/drei'
<Detailed distances={[0, 10, 20]}>
{/* High detail - close to camera */}
<mesh geometry={highPolyGeometry} />
{/* Medium detail */}
<mesh geometry={mediumPolyGeometry} />
{/* Low detail - far from camera */}
<mesh geometry={lowPolyGeometry} />
</Detailed>import { AdaptiveDpr, AdaptiveEvents, PerformanceMonitor } from '@react-three/drei'
<Canvas>
{/* Reduce DPR when performance drops */}
<AdaptiveDpr pixelated />
{/* Reduce raycast frequency */}
<AdaptiveEvents />
{/* Monitor and respond to performance */}
<PerformanceMonitor
onIncline={() => console.log('Performance improved')}
onDecline={() => console.log('Performance degraded')}
>
<Scene />
</PerformanceMonitor>
</Canvas>useThree// ❌ Re-renders on any state change
const state = useThree()
// ✅ Only re-renders when size changes
const size = useThree((state) => state.size)
// ✅ Only re-renders when camera changes
const camera = useThree((state) => state.camera)// ❌ BAD: Triggers React re-renders every frame
const [x, setX] = useState(0)
useFrame(() => setX((x) => x + 0.1))
return <mesh position-x={x} />// ✅ GOOD: Direct mutation, no re-renders
const meshRef = useRef()
useFrame((state, delta) => {
meshRef.current.position.x += delta
})
return <mesh ref={meshRef} />// ❌ BAD: Creates new Vector3 every render
<mesh position={new THREE.Vector3(1, 2, 3)} />// ✅ GOOD: Use array notation
<mesh position={[1, 2, 3]} />
// Or useMemo for complex objects
const position = useMemo(() => new THREE.Vector3(1, 2, 3), [])
<mesh position={position} />// ❌ BAD: Loads texture every render
function Component() {
const [texture, setTexture] = useState()
useEffect(() => {
new TextureLoader().load('/texture.jpg', setTexture)
}, [])
return texture ? <meshBasicMaterial map={texture} /> : null
}// ✅ GOOD: Cached and reused
function Component() {
const texture = useLoader(TextureLoader, '/texture.jpg')
return <meshBasicMaterial map={texture} />
}// ❌ BAD: Unmounts and remounts (expensive)
{stage === 1 && <Stage1 />}
{stage === 2 && <Stage2 />}
{stage === 3 && <Stage3 />}// ✅ GOOD: Components stay mounted, just hidden
<Stage1 visible={stage === 1} />
<Stage2 visible={stage === 2} />
<Stage3 visible={stage === 3} />
function Stage1({ visible, ...props }) {
return <group {...props} visible={visible}>...</group>
}// ❌ BAD: Crashes - useThree must be inside Canvas
function App() {
const { size } = useThree()
return <Canvas>...</Canvas>
}// ✅ GOOD: useThree inside Canvas child
function CameraInfo() {
const { size } = useThree()
return null
}
function App() {
return (
<Canvas>
<CameraInfo />
</Canvas>
)
}// ❌ BAD: Memory leak - textures not disposed
const texture = useLoader(TextureLoader, '/texture.jpg')// ✅ GOOD: Manual cleanup when needed
useEffect(() => {
const geometry = new THREE.SphereGeometry(1)
const material = new THREE.MeshBasicMaterial()
return () => {
geometry.dispose()
material.dispose()
}
}, [])function Lights() {
return (
<>
<ambientLight intensity={0.5} />
<spotLight position={[10, 10, 10]} angle={0.15} />
</>
)
}
function Scene() {
return (
<>
<Lights />
<Model />
<Ground />
<Effects />
</>
)
}
<Canvas>
<Scene />
</Canvas><Canvas>
<Suspense fallback={<Loader />}>
<Model />
<Environment />
</Suspense>
</Canvas>import { ThreeElements } from '@react-three/fiber'
function Box(props: ThreeElements['mesh']) {
return (
<mesh {...props}>
<boxGeometry />
<meshStandardMaterial />
</mesh>
)
}src/
components/
3d/
Scene.tsx
Lights.tsx
Camera.tsx
models/
Robot.tsx
Character.tsx
effects/
PostProcessing.tsxreferences/api_reference.mdreferences/hooks_guide.mdreferences/drei_helpers.mdscripts/component_generator.pyscripts/scene_setup.pyassets/starter_r3f/assets/examples/