
React Three Fiber
- 1.9k installs
- 629 repo stars
- Updated November 20, 2025
- freshtechbro/claudedesignskills
react-three-fiber is an agent skill that 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 a
About
react-three-fiber is an agent skill from freshtechbro/claudedesignskills that 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 ma. # React Three Fiber ## Overview React Three Fiber (R3F) is a React renderer for Three.js that brings declarative, component-based 3D development to React applications. Instead of imperatively creating and managing Three.js objects, you build 3D scenes using JSX components that map directly to Three.js objects. **When to Use This Skill**: - Build Developers invoke react-three-fiber during build/frontend work for frontend development tasks. The skill documents triggers, prerequisites, and step-by-step workflows grounded in SKILL.md. Compatible with Claude Code, Cursor, and Codex agent runtimes that load marketplace skills.
- Building 3D experiences within React applications
- Creating interactive product configurators or showcases
- Developing 3D portfolios, galleries, or storytelling experiences
- Building games or simulations in React
- Adding 3D elements to existing React projects
React Three Fiber by the numbers
- 1,916 all-time installs (skills.sh)
- +147 installs in the week ending Jul 29, 2026 (Skillselion tracking)
- Ranked #238 of 2,244 Frontend Development skills by installs in the Skillselion catalog
- Security screen: LOW risk (skills.sh audit)
- Data as of Jul 31, 2026 (Skillselion catalog sync)
react-three-fiber capabilities & compatibility
- Capabilities
- building 3d experiences within react application · creating interactive product configurators or sh · developing 3d portfolios, galleries, or storytel · building games or simulations in react · adding 3d elements to existing react projects
- Use cases
- orchestration
What react-three-fiber says it does
- Building 3D experiences within React applications
- Creating interactive product configurators or showcases
- Developing 3D portfolios, galleries, or storytelling experiences
npx skills add https://github.com/freshtechbro/claudedesignskills --skill react-three-fiberAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1.9k |
|---|---|
| repo stars | ★ 629 |
| Security audit | 3 / 3 scanners passed |
| Last updated | November 20, 2025 |
| Repository | freshtechbro/claudedesignskills ↗ |
What it does
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 ma
Who is it for?
Developers working on frontend development during build tasks.
Skip if: Tasks outside Frontend Development scope described in SKILL.md.
When should I use this skill?
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 ma
What you get
Completed frontend development workflow aligned with SKILL.md steps.
- R3F component implementations
- 3D scene interaction patterns
By the numbers
- Covers 10 documented React Three Fiber component pattern areas
Files
React Three Fiber
Overview
React Three Fiber (R3F) is a React renderer for Three.js that brings declarative, component-based 3D development to React applications. Instead of imperatively creating and managing Three.js objects, you build 3D scenes using JSX components that map directly to Three.js objects.
When to Use This Skill:
- Building 3D experiences within React applications
- Creating interactive product configurators or showcases
- Developing 3D portfolios, galleries, or storytelling experiences
- Building games or simulations in React
- Adding 3D elements to existing React projects
- When you need state management and React hooks with 3D graphics
- When working with React frameworks (Next.js, Gatsby, Remix)
Key Benefits:
- Declarative: Write 3D scenes like React components
- React Integration: Full access to hooks, context, state management
- Reusability: Create and share 3D component libraries
- Performance: Automatic render optimization and reconciliation
- Ecosystem: Works with Drei helpers, Zustand, Framer Motion, etc.
- TypeScript Support: Full type safety for Three.js objects
---
Core Concepts
1. Canvas Component
The <Canvas> component sets up a Three.js scene, camera, renderer, and render loop.
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>
)
}Canvas Props:
camera- Camera configuration (position, fov, near, far)gl- WebGL renderer settingsdpr- Device pixel ratio (default: [1, 2])shadows- Enable shadow mapping (default: false)frameloop- "always" (default), "demand", or "never"flat- Disable color management for simpler colorslinear- Use linear color space instead of sRGB
2. Declarative 3D Objects
Three.js objects are created using JSX with kebab-case props:
// 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>Prop Mapping:
position→object.position.set(x, y, z)rotation→object.rotation.set(x, y, z)scale→object.scale.set(x, y, z)args→ Constructor arguments for geometry/materialattach→ Attach to parent property (e.g.,attach="material")
Shorthand Notation:
// Full notation
<mesh position={[1, 2, 3]} />
// Axis-specific (dash notation)
<mesh position-x={1} position-y={2} position-z={3} />3. useFrame Hook
Execute code on every frame (animation loop):
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>
)
}useFrame Parameters:
state- Scene state (camera, scene, gl, clock, etc.)delta- Time since last frame (for frame-rate independence)xrFrame- XR frame data (for VR/AR)
Important: Never use setState inside useFrame - it causes unnecessary re-renders!
4. useThree Hook
Access scene state and methods:
import { 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
}Available State:
camera- Default camerascene- Three.js scenegl- WebGL renderersize- Canvas dimensionsviewport- Viewport dimensions in 3D unitsclock- Three.js clockpointer- Normalized mouse coordinatesinvalidate()- Manually trigger rendersetSize()- Manually resize canvas
5. useLoader Hook
Load assets with automatic caching and Suspense integration:
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>
)
}Loading Multiple Assets:
const [texture1, texture2, texture3] = useLoader(TextureLoader, [
'/tex1.jpg',
'/tex2.jpg',
'/tex3.jpg'
])Loader Extensions:
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader'
useLoader(GLTFLoader, '/model.glb', (loader) => {
const dracoLoader = new DRACOLoader()
dracoLoader.setDecoderPath('/draco/')
loader.setDRACOLoader(dracoLoader)
})Pre-loading:
// Pre-load assets before component mounts
useLoader.preload(GLTFLoader, '/model.glb')---
Common Patterns
Pattern 1: Basic Scene Setup
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>
)
}Pattern 2: Interactive Objects (Click, Hover)
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>
)
}Pattern 3: Animated Component with useFrame
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>
)
}Pattern 4: Loading GLTF Models
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>
)
}Pattern 5: Multiple Lights
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}
/>
</>
)
}Pattern 6: Instancing (Many Objects)
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>
)
}Pattern 7: Groups and Nesting
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>
)
}---
Integration with Drei Helpers
Drei is the essential helper library for R3F, providing ready-to-use components:
OrbitControls
import { OrbitControls } from '@react-three/drei'
<Canvas>
<OrbitControls
makeDefault
enableDamping
dampingFactor={0.05}
minDistance={3}
maxDistance={20}
/>
<Box />
</Canvas>Environment & Lighting
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>Text
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>useGLTF Hook (Drei)
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')Center & Bounds
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>HTML Overlay
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>Scroll Controls
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>---
Integration with Other Libraries
With GSAP
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>
)
}With Framer Motion
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>
)
}With Zustand (State Management)
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>
)
}---
Performance Optimization
1. On-Demand Rendering
<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>
)
}2. Instancing
Use <instancedMesh> for rendering many identical objects:
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>
)
}3. Frustum Culling
Objects outside the camera view are automatically culled.
// Disable for always-visible objects
<mesh frustumCulled={false}>
<boxGeometry />
<meshStandardMaterial />
</mesh>4. LOD (Level of Detail)
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>5. Adaptive Performance
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>6. Selective Re-renders
Use useThree selectors to avoid unnecessary re-renders:
// ❌ 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)---
Common Pitfalls & Solutions
❌ Pitfall 1: setState in useFrame
// ❌ BAD: Triggers React re-renders every frame
const [x, setX] = useState(0)
useFrame(() => setX((x) => x + 0.1))
return <mesh position-x={x} />✅ Solution: Mutate refs directly
// ✅ GOOD: Direct mutation, no re-renders
const meshRef = useRef()
useFrame((state, delta) => {
meshRef.current.position.x += delta
})
return <mesh ref={meshRef} />❌ Pitfall 2: Creating Objects in Render
// ❌ BAD: Creates new Vector3 every render
<mesh position={new THREE.Vector3(1, 2, 3)} />✅ Solution: Use arrays or useMemo
// ✅ 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} />❌ Pitfall 3: Not Using useLoader Cache
// ❌ BAD: Loads texture every render
function Component() {
const [texture, setTexture] = useState()
useEffect(() => {
new TextureLoader().load('/texture.jpg', setTexture)
}, [])
return texture ? <meshBasicMaterial map={texture} /> : null
}✅ Solution: Use useLoader (automatic caching)
// ✅ GOOD: Cached and reused
function Component() {
const texture = useLoader(TextureLoader, '/texture.jpg')
return <meshBasicMaterial map={texture} />
}❌ Pitfall 4: Conditional Mounting (Expensive)
// ❌ BAD: Unmounts and remounts (expensive)
{stage === 1 && <Stage1 />}
{stage === 2 && <Stage2 />}
{stage === 3 && <Stage3 />}✅ Solution: Use visibility prop
// ✅ 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>
}❌ Pitfall 5: useThree Outside Canvas
// ❌ BAD: Crashes - useThree must be inside Canvas
function App() {
const { size } = useThree()
return <Canvas>...</Canvas>
}✅ Solution: Use hooks inside Canvas children
// ✅ GOOD: useThree inside Canvas child
function CameraInfo() {
const { size } = useThree()
return null
}
function App() {
return (
<Canvas>
<CameraInfo />
</Canvas>
)
}❌ Pitfall 6: Not Disposing Resources
// ❌ BAD: Memory leak - textures not disposed
const texture = useLoader(TextureLoader, '/texture.jpg')✅ Solution: R3F handles disposal automatically, but be careful with manual Three.js objects
// ✅ GOOD: Manual cleanup when needed
useEffect(() => {
const geometry = new THREE.SphereGeometry(1)
const material = new THREE.MeshBasicMaterial()
return () => {
geometry.dispose()
material.dispose()
}
}, [])---
Best Practices
1. Component Composition
Break scenes into reusable components:
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>2. Suspend Heavy Assets
Always wrap async operations in Suspense:
<Canvas>
<Suspense fallback={<Loader />}>
<Model />
<Environment />
</Suspense>
</Canvas>3. Use TypeScript
import { ThreeElements } from '@react-three/fiber'
function Box(props: ThreeElements['mesh']) {
return (
<mesh {...props}>
<boxGeometry />
<meshStandardMaterial />
</mesh>
)
}4. Organize by Feature
src/
components/
3d/
Scene.tsx
Lights.tsx
Camera.tsx
models/
Robot.tsx
Character.tsx
effects/
PostProcessing.tsx5. Test with React DevTools Profiler
Monitor re-renders and optimize components causing performance issues.
---
Resources
References
references/api_reference.md- Complete R3F & Drei API documentationreferences/hooks_guide.md- Detailed hooks usage and patternsreferences/drei_helpers.md- Comprehensive Drei library guide
Scripts
scripts/component_generator.py- Generate R3F component boilerplatescripts/scene_setup.py- Initialize R3F scene with common patterns
Assets
assets/starter_r3f/- Complete R3F + Vite starter templateassets/examples/- Real-world R3F component examples
External Resources
- Official Docs
- Drei Docs
- Three.js Docs
- R3F Discord
- Poimandres (pmnd.rs) - Ecosystem overview
React Three Fiber - Real-World Component Examples
A comprehensive collection of production-ready R3F component patterns and examples.
Table of Contents
1. GLTF Model Loading 2. Interactive Product Viewer 3. Scroll-Based Animations 4. Particle System 5. Text 3D 6. Post-Processing Effects 7. Physics Simulation 8. Camera Animations 9. LOD (Level of Detail) 10. Performance Monitoring
---
1. GLTF Model Loading
Basic Model Loader
import { useGLTF } from '@react-three/drei'
import { Suspense } from 'react'
function Model({ url, ...props }) {
const { scene } = useGLTF(url)
return <primitive object={scene} {...props} />
}
// Preload for faster initial load
useGLTF.preload('/models/scene.glb')
export default function Scene() {
return (
<Suspense fallback={<Loader />}>
<Model url="/models/scene.glb" position={[0, 0, 0]} scale={0.5} />
</Suspense>
)
}
function Loader() {
return (
<mesh>
<boxGeometry args={[1, 1, 1]} />
<meshBasicMaterial wireframe color="white" />
</mesh>
)
}Model with Animations
import { useGLTF, useAnimations } from '@react-three/drei'
import { useEffect, useRef } from 'react'
function AnimatedModel({ url }) {
const group = useRef()
const { scene, animations } = useGLTF(url)
const { actions, names } = useAnimations(animations, group)
useEffect(() => {
// Play first animation
actions[names[0]]?.play()
}, [actions, names])
return <primitive ref={group} object={scene} />
}
useGLTF.preload('/models/animated.glb')Model with Material Override
import { useGLTF } from '@react-three/drei'
import { useEffect } from 'react'
import * as THREE from 'three'
function ModelWithCustomMaterial({ url }) {
const { scene } = useGLTF(url)
useEffect(() => {
const customMaterial = new THREE.MeshStandardMaterial({
color: '#ff6b6b',
metalness: 0.8,
roughness: 0.2,
})
scene.traverse((child) => {
if (child.isMesh) {
child.material = customMaterial
child.castShadow = true
child.receiveShadow = true
}
})
}, [scene])
return <primitive object={scene} />
}---
2. Interactive Product Viewer
import { useRef, useState } from 'react'
import { Canvas, useFrame } from '@react-three/fiber'
import { OrbitControls, Environment, useGLTF, Center, Bounds } from '@react-three/drei'
import { useControls } from 'leva'
function Product({ url }) {
const { scene } = useGLTF(url)
const meshRef = useRef()
const [hovered, setHovered] = useState(false)
// Material controls
const { metalness, roughness, color } = useControls({
metalness: { value: 0.9, min: 0, max: 1, step: 0.01 },
roughness: { value: 0.1, min: 0, max: 1, step: 0.01 },
color: '#ffffff',
})
useFrame((state) => {
if (hovered) {
meshRef.current.rotation.y += 0.01
}
})
return (
<Center>
<primitive
ref={meshRef}
object={scene}
onPointerOver={() => setHovered(true)}
onPointerOut={() => setHovered(false)}
onClick={(e) => {
e.stopPropagation()
console.log('Product clicked:', e.object.name)
}}
/>
</Center>
)
}
export default function ProductViewer() {
return (
<Canvas camera={{ position: [0, 0, 5], fov: 45 }}>
<color attach="background" args={['#f0f0f0']} />
<ambientLight intensity={0.5} />
<directionalLight position={[5, 5, 5]} intensity={1} />
<Suspense fallback={null}>
<Bounds fit clip observe margin={1.2}>
<Product url="/models/product.glb" />
</Bounds>
<Environment preset="studio" />
</Suspense>
<OrbitControls
enablePan={false}
minDistance={2}
maxDistance={10}
minPolarAngle={Math.PI / 4}
maxPolarAngle={Math.PI / 2}
/>
</Canvas>
)
}---
3. Scroll-Based Animations
Using Drei ScrollControls
import { Canvas } from '@react-three/fiber'
import { ScrollControls, Scroll, useScroll } from '@react-three/drei'
import { useFrame } from '@react-three/fiber'
import { useRef } from 'react'
function ScrollScene() {
const meshRef = useRef()
const scroll = useScroll()
useFrame(() => {
// scroll.offset: 0 to 1
meshRef.current.position.y = scroll.offset * -10
meshRef.current.rotation.y = scroll.offset * Math.PI * 2
})
return (
<mesh ref={meshRef}>
<boxGeometry />
<meshStandardMaterial color="orange" />
</mesh>
)
}
export default function ScrollAnimation() {
return (
<Canvas>
<ambientLight intensity={0.5} />
<directionalLight position={[5, 5, 5]} />
<ScrollControls pages={3} damping={0.1}>
<Scroll>
<ScrollScene />
</Scroll>
{/* HTML content that scrolls */}
<Scroll html>
<div style={{ height: '100vh' }}>
<h1>Page 1</h1>
</div>
<div style={{ height: '100vh' }}>
<h1>Page 2</h1>
</div>
<div style={{ height: '100vh' }}>
<h1>Page 3</h1>
</div>
</Scroll>
</ScrollControls>
</Canvas>
)
}Syncing with GSAP ScrollTrigger
import { useRef, useEffect } from 'react'
import { useFrame, useThree } from '@react-three/fiber'
import gsap from 'gsap'
import { ScrollTrigger } from 'gsap/ScrollTrigger'
gsap.registerPlugin(ScrollTrigger)
function ScrollSyncedBox() {
const meshRef = useRef()
const { viewport } = useThree()
useEffect(() => {
const ctx = gsap.context(() => {
gsap.to(meshRef.current.position, {
x: 5,
scrollTrigger: {
trigger: '.section-2',
start: 'top center',
end: 'bottom center',
scrub: true,
},
})
gsap.to(meshRef.current.rotation, {
y: Math.PI * 2,
scrollTrigger: {
trigger: '.section-2',
start: 'top center',
end: 'bottom center',
scrub: true,
},
})
})
return () => ctx.revert()
}, [])
return (
<mesh ref={meshRef}>
<boxGeometry />
<meshStandardMaterial color="orange" />
</mesh>
)
}---
4. Particle System
Instanced Particles
import { useRef, useMemo } from 'react'
import { useFrame } from '@react-three/fiber'
import * as THREE from 'three'
export default function Particles({ count = 5000 }) {
const meshRef = useRef()
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() * 40 - 20
const y = Math.random() * 40 - 20
const z = Math.random() * 40 - 20
temp.push({ t, factor, speed, x, y, z })
}
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.rotation.set(s * 5, s * 5, s * 5)
dummy.updateMatrix()
meshRef.current.setMatrixAt(i, dummy.matrix)
})
meshRef.current.instanceMatrix.needsUpdate = true
})
return (
<instancedMesh ref={meshRef} args={[null, null, count]}>
<dodecahedronGeometry args={[0.1, 0]} />
<meshPhongMaterial color="#ff4040" />
</instancedMesh>
)
}---
5. Text 3D
Using Drei Text
import { Text } from '@react-three/drei'
import { useRef } from 'react'
import { useFrame } from '@react-three/fiber'
export default function Text3D() {
const textRef = useRef()
useFrame((state) => {
textRef.current.position.y = Math.sin(state.clock.elapsedTime) * 0.5
})
return (
<Text
ref={textRef}
fontSize={1}
color="#ffffff"
anchorX="center"
anchorY="middle"
font="/fonts/Inter-Bold.woff"
>
Hello R3F!
</Text>
)
}Text with Gradient Material
import { Text, shaderMaterial } from '@react-three/drei'
import { extend } from '@react-three/fiber'
import * as THREE from 'three'
const GradientMaterial = shaderMaterial(
{ uTime: 0 },
// Vertex shader
`
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
// Fragment shader
`
uniform float uTime;
varying vec2 vUv;
void main() {
vec3 colorA = vec3(1.0, 0.4, 0.7);
vec3 colorB = vec3(0.2, 0.6, 1.0);
vec3 color = mix(colorA, colorB, vUv.y);
gl_FragColor = vec4(color, 1.0);
}
`
)
extend({ GradientMaterial })
export default function GradientText() {
const materialRef = useRef()
useFrame((state) => {
materialRef.current.uTime = state.clock.elapsedTime
})
return (
<Text fontSize={1} anchorX="center" anchorY="middle">
Gradient Text
<gradientMaterial ref={materialRef} />
</Text>
)
}---
6. Post-Processing Effects
npm install @react-three/postprocessingimport { Canvas } from '@react-three/fiber'
import { EffectComposer, Bloom, DepthOfField, Vignette } from '@react-three/postprocessing'
export default function PostProcessingScene() {
return (
<Canvas>
<ambientLight intensity={0.5} />
<directionalLight position={[5, 5, 5]} />
<mesh>
<torusKnotGeometry />
<meshStandardMaterial color="orange" emissive="orange" emissiveIntensity={0.5} />
</mesh>
<EffectComposer>
<Bloom
intensity={1.5}
luminanceThreshold={0}
luminanceSmoothing={0.9}
/>
<DepthOfField
focusDistance={0}
focalLength={0.02}
bokehScale={2}
/>
<Vignette
offset={0.5}
darkness={0.5}
/>
</EffectComposer>
</Canvas>
)
}---
7. Physics Simulation
npm install @react-three/rapierimport { Canvas } from '@react-three/fiber'
import { Physics, RigidBody, CuboidCollider } from '@react-three/rapier'
function Box({ position }) {
return (
<RigidBody position={position} colliders="cuboid">
<mesh castShadow>
<boxGeometry />
<meshStandardMaterial color="orange" />
</mesh>
</RigidBody>
)
}
function Floor() {
return (
<RigidBody type="fixed">
<CuboidCollider args={[10, 0.5, 10]} />
<mesh receiveShadow position={[0, -0.5, 0]}>
<boxGeometry args={[20, 1, 20]} />
<meshStandardMaterial color="#808080" />
</mesh>
</RigidBody>
)
}
export default function PhysicsScene() {
return (
<Canvas shadows camera={{ position: [0, 5, 10] }}>
<ambientLight intensity={0.5} />
<directionalLight position={[5, 5, 5]} castShadow />
<Physics gravity={[0, -9.81, 0]}>
<Box position={[0, 5, 0]} />
<Box position={[1, 8, 0]} />
<Box position={[-1, 10, 0]} />
<Floor />
</Physics>
</Canvas>
)
}---
8. Camera Animations
Smooth Camera Movement
import { useRef } from 'react'
import { useFrame, useThree } from '@react-three/fiber'
import * as THREE from 'three'
export default function CameraRig({ children }) {
const ref = useRef()
const { camera, pointer } = useThree()
useFrame((state, delta) => {
// Smooth camera follow mouse
const targetX = pointer.x * 2
const targetY = pointer.y * 2
camera.position.x += (targetX - camera.position.x) * delta * 2
camera.position.y += (targetY - camera.position.y) * delta * 2
camera.lookAt(0, 0, 0)
})
return <group ref={ref}>{children}</group>
}Camera Path Animation
import { useEffect, useRef } from 'react'
import { useThree } from '@react-three/fiber'
import * as THREE from 'three'
import gsap from 'gsap'
export default function CameraPath() {
const { camera } = useThree()
const pathRef = useRef()
useEffect(() => {
// Define camera path points
const points = [
new THREE.Vector3(0, 0, 5),
new THREE.Vector3(5, 2, 5),
new THREE.Vector3(5, 2, -5),
new THREE.Vector3(-5, 2, -5),
new THREE.Vector3(-5, 2, 5),
new THREE.Vector3(0, 0, 5),
]
const curve = new THREE.CatmullRomCurve3(points)
pathRef.current = curve
// Animate camera along path
gsap.to({ progress: 0 }, {
progress: 1,
duration: 10,
repeat: -1,
ease: 'none',
onUpdate: function() {
const point = curve.getPointAt(this.targets()[0].progress)
camera.position.copy(point)
camera.lookAt(0, 0, 0)
}
})
}, [camera])
return null
}---
9. LOD (Level of Detail)
import { useMemo } from 'react'
import { useThree } from '@react-three/fiber'
import * as THREE from 'three'
export default function LODMesh({ position }) {
const { camera } = useThree()
const lod = useMemo(() => {
const lodObject = new THREE.LOD()
// High detail (close)
const geometryHigh = new THREE.IcosahedronGeometry(1, 3)
const materialHigh = new THREE.MeshStandardMaterial({ color: 'orange' })
const meshHigh = new THREE.Mesh(geometryHigh, materialHigh)
lodObject.addLevel(meshHigh, 0)
// Medium detail
const geometryMid = new THREE.IcosahedronGeometry(1, 1)
const materialMid = new THREE.MeshStandardMaterial({ color: 'orange' })
const meshMid = new THREE.Mesh(geometryMid, materialMid)
lodObject.addLevel(meshMid, 10)
// Low detail (far)
const geometryLow = new THREE.IcosahedronGeometry(1, 0)
const materialLow = new THREE.MeshStandardMaterial({ color: 'orange' })
const meshLow = new THREE.Mesh(geometryLow, materialLow)
lodObject.addLevel(meshLow, 20)
lodObject.position.set(...position)
return lodObject
}, [position])
useFrame(() => {
lod.update(camera)
})
return <primitive object={lod} />
}---
10. Performance Monitoring
import { Canvas } from '@react-three/fiber'
import {
PerformanceMonitor,
AdaptiveDpr,
AdaptiveEvents,
Stats
} from '@react-three/drei'
import { useState } from 'react'
export default function AdaptiveScene() {
const [dpr, setDpr] = useState(1.5)
return (
<>
<Stats />
<Canvas dpr={dpr}>
<PerformanceMonitor
onIncline={() => setDpr(2)}
onDecline={() => setDpr(1)}
>
<AdaptiveDpr pixelated />
<AdaptiveEvents />
{/* Your scene */}
<ambientLight intensity={0.5} />
<mesh>
<torusKnotGeometry args={[1, 0.3, 128, 32]} />
<meshStandardMaterial color="orange" />
</mesh>
</PerformanceMonitor>
</Canvas>
</>
)
}---
Additional Resources
- Poimandres Market - Ready-to-use R3F components
- Three.js Journey - Comprehensive Three.js course
- R3F Examples - Official examples
- Codesandbox Collection - Live examples
Performance Best Practices
1. Use instancing for many similar objects 2. Implement LOD for distant objects 3. Enable frustum culling (automatic in R3F) 4. Use adaptive DPR for mobile devices 5. Lazy load models with Suspense 6. Optimize textures (compress, use power-of-2 sizes) 7. Reduce shadow quality on low-end devices 8. Use frameloop="demand" for static scenes 9. Profile with Stats and Chrome DevTools 10. Dispose unused resources properly
---
Note: All examples assume you have the necessary dependencies installed. Refer to each example's comments for additional package requirements.
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>React Three Fiber Starter</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html,
body,
#root {
width: 100%;
height: 100%;
overflow: hidden;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.loading {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
background: #000;
color: #fff;
font-size: 24px;
}
.info {
position: absolute;
top: 20px;
left: 20px;
color: white;
font-size: 14px;
background: rgba(0, 0, 0, 0.7);
padding: 10px 15px;
border-radius: 5px;
z-index: 10;
}
.info h1 {
font-size: 18px;
margin-bottom: 5px;
}
.info p {
margin: 3px 0;
opacity: 0.8;
}
</style>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>
{
"name": "r3f-starter",
"private": true,
"version": "0.1.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"react": "^18.3.1",
"react-dom": "^18.3.1",
"@react-three/fiber": "^8.18.8",
"@react-three/drei": "^9.123.0",
"three": "^0.172.0",
"leva": "^0.9.35"
},
"devDependencies": {
"@types/react": "^18.3.18",
"@types/react-dom": "^18.3.5",
"@types/three": "^0.172.0",
"@vitejs/plugin-react": "^4.3.4",
"vite": "^6.0.11"
}
}
React Three Fiber Starter Template
A minimal, production-ready starter template for building 3D experiences with React Three Fiber (R3F), Drei helpers, and Vite.
Features
- ⚡️ Vite - Fast build tool and dev server
- ⚛️ React 18 - Latest React with concurrent features
- 🎨 React Three Fiber - Declarative Three.js in React
- 🛠️ Drei - Essential R3F helpers (OrbitControls, Environment, etc.)
- 🎮 Interactive Components - Click and hover interactions
- 🌅 Environment & Lighting - HDRI environment with proper lighting
- 💫 Animations - useFrame examples for smooth 60fps animations
- 📦 Optimized Build - Code-splitting for Three.js and R3F
- 🎛️ Leva - Optional GUI controls for debugging
Quick Start
Installation
npm install
# or
yarn
# or
pnpm installDevelopment
npm run devOpens at http://localhost:3000
Build
npm run buildPreview Production Build
npm run previewProject Structure
starter_r3f/
├── index.html # Entry HTML with full-page canvas styling
├── package.json # Dependencies and scripts
├── vite.config.js # Vite configuration with code-splitting
└── src/
├── main.jsx # React root
├── App.jsx # Main App component with UI overlay
├── Experience.jsx # Canvas wrapper and Scene component
└── components/
├── Box.jsx # Interactive box with click/hover
└── Sphere.jsx # Floating animated sphereWhat's Included
Components
Box.jsx - Interactive component with:
- Click to toggle rotation animation
- Hover for color change
- Scale animation on click
- Cast shadows
Sphere.jsx - Animated component with:
- Floating sine wave animation
- Continuous rotation
- Metallic material
- Cast shadows
Scene Setup (Experience.jsx)
- Lighting: Ambient + Directional with shadows
- Environment: Drei Environment preset (HDRI)
- Shadows: Contact shadows for soft ground shadows
- Controls: OrbitControls for camera manipulation
- Ground Plane: Receives shadows
Performance Features
- Code-splitting: Separate chunks for Three.js and R3F
- Suspense: Async loading with fallback
- Optimized imports: Tree-shaking friendly imports
Customization
Change Environment Preset
// Experience.jsx
<Environment preset="sunset" /> // Try: city, forest, night, warehouse, etc.Add New Components
// src/components/MyComponent.jsx
import { useRef } from 'react'
import { useFrame } from '@react-three/fiber'
export default function MyComponent() {
const meshRef = useRef()
useFrame((state, delta) => {
meshRef.current.rotation.y += delta
})
return (
<mesh ref={meshRef}>
<boxGeometry />
<meshStandardMaterial color="orange" />
</mesh>
)
}Then import in Experience.jsx:
import MyComponent from './components/MyComponent'
// In Scene component:
<MyComponent />Add Leva Controls
import { useControls } from 'leva'
export default function Box() {
const { color, scale } = useControls({
color: '#ff6b6b',
scale: { value: 1, min: 0.5, max: 2, step: 0.1 }
})
return (
<mesh scale={scale}>
<boxGeometry />
<meshStandardMaterial color={color} />
</mesh>
)
}Common Patterns
Load GLTF Models
import { useGLTF } from '@react-three/drei'
function Model() {
const { scene } = useGLTF('/models/mymodel.glb')
return <primitive object={scene} />
}
// Preload
useGLTF.preload('/models/mymodel.glb')Add Post-Processing
npm install @react-three/postprocessingimport { EffectComposer, Bloom } from '@react-three/postprocessing'
<Canvas>
<Scene />
<EffectComposer>
<Bloom intensity={0.5} />
</EffectComposer>
</Canvas>Optimize Performance
// Enable adaptive pixel ratio
<Canvas dpr={[1, 2]} />
// Use on-demand rendering for static scenes
<Canvas frameloop="demand" />
// Use instancing for many objects
import { Instance, Instances } from '@react-three/drei'
<Instances>
<boxGeometry />
<meshStandardMaterial />
<Instance position={[0, 0, 0]} />
<Instance position={[2, 0, 0]} />
{/* ... thousands more */}
</Instances>Resources
Troubleshooting
Canvas Not Rendering
- Ensure
#roothas width and height set (see index.html styles) - Check browser console for errors
- Verify Three.js and R3F versions are compatible
Performance Issues
- Reduce shadow quality:
shadow-mapSize={[512, 512]} - Disable shadows: Remove
castShadowandreceiveShadow - Use adaptive DPR:
<Canvas dpr={[1, 2]} /> - Enable frameloop demand:
<Canvas frameloop="demand" />
TypeScript Support
npm install -D typescript @types/react @types/react-dom @types/threeRename files to .tsx and add tsconfig.json:
{
"compilerOptions": {
"target": "ES2020",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"moduleResolution": "bundler",
"jsx": "react-jsx",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true
},
"include": ["src"]
}License
MIT - Use freely for personal and commercial projects.
import Experience from './Experience'
export default function App() {
return (
<>
<div className="info">
<h1>React Three Fiber Starter</h1>
<p>🖱️ Drag to rotate</p>
<p>🔍 Scroll to zoom</p>
</div>
<Experience />
</>
)
}
import { useRef, useState } from 'react'
import { useFrame } from '@react-three/fiber'
export default function Box({ position = [0, 0, 0], ...props }) {
const meshRef = useRef()
const [hovered, setHovered] = useState(false)
const [active, setActive] = useState(false)
// Animate on every frame
useFrame((state, delta) => {
if (active) {
meshRef.current.rotation.x += delta
meshRef.current.rotation.y += delta * 0.5
}
})
return (
<mesh
{...props}
ref={meshRef}
position={position}
scale={active ? 1.5 : 1}
onClick={(e) => {
e.stopPropagation()
setActive(!active)
}}
onPointerOver={(e) => {
e.stopPropagation()
setHovered(true)
}}
onPointerOut={() => setHovered(false)}
castShadow
>
<boxGeometry args={[1, 1, 1]} />
<meshStandardMaterial color={hovered ? 'hotpink' : 'orange'} />
</mesh>
)
}
import { useRef } from 'react'
import { useFrame } from '@react-three/fiber'
export default function Sphere({ position = [0, 0, 0], ...props }) {
const meshRef = useRef()
// Floating animation
useFrame((state) => {
const time = state.clock.elapsedTime
meshRef.current.position.y = position[1] + Math.sin(time * 2) * 0.5
meshRef.current.rotation.y = time * 0.5
})
return (
<mesh {...props} ref={meshRef} position={position} castShadow>
<sphereGeometry args={[0.75, 32, 32]} />
<meshStandardMaterial
color="skyblue"
metalness={0.6}
roughness={0.2}
/>
</mesh>
)
}
import { Canvas } from '@react-three/fiber'
import { OrbitControls, Environment, ContactShadows } from '@react-three/drei'
import { Suspense } from 'react'
import Box from './components/Box'
import Sphere from './components/Sphere'
function Scene() {
return (
<>
{/* Lighting */}
<ambientLight intensity={0.5} />
<directionalLight
position={[5, 5, 5]}
intensity={1}
castShadow
shadow-mapSize={[1024, 1024]}
/>
{/* 3D Objects */}
<Box position={[-2, 1, 0]} />
<Sphere position={[2, 1, 0]} />
{/* Ground */}
<mesh rotation={[-Math.PI / 2, 0, 0]} position={[0, 0, 0]} receiveShadow>
<planeGeometry args={[10, 10]} />
<meshStandardMaterial color="#808080" />
</mesh>
{/* Environment */}
<Environment preset="sunset" />
<ContactShadows
position={[0, 0, 0]}
opacity={0.5}
scale={10}
blur={1}
far={10}
/>
{/* Camera Controls */}
<OrbitControls makeDefault />
</>
)
}
export default function Experience() {
return (
<Canvas
shadows
camera={{ position: [5, 5, 5], fov: 50 }}
style={{ width: '100%', height: '100%' }}
>
<Suspense fallback={null}>
<Scene />
</Suspense>
</Canvas>
)
}
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import App from './App.jsx'
createRoot(document.getElementById('root')).render(
<StrictMode>
<App />
</StrictMode>,
)
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
// https://vite.dev/config/
export default defineConfig({
plugins: [react()],
server: {
host: true,
port: 3000,
},
build: {
rollupOptions: {
output: {
manualChunks: {
'three': ['three'],
'r3f': ['@react-three/fiber', '@react-three/drei'],
},
},
},
},
})
React Three Fiber API Reference
Complete API reference for React Three Fiber core and essential Drei helpers.
---
Table of Contents
1. Canvas Component 2. Hooks 3. Events 4. Three.js Object Props 5. Drei Helpers
---
Canvas Component
The <Canvas> component is the root of every R3F scene. It sets up the renderer, scene, and camera.
Props
interface CanvasProps {
children: React.ReactNode
// Rendering
gl?: Partial<WebGLRendererParameters> | ((canvas: HTMLCanvasElement) => WebGLRenderer)
dpr?: number | [min: number, max: number]
frameloop?: 'always' | 'demand' | 'never'
flat?: boolean
linear?: boolean
legacy?: boolean
// Camera
camera?: Partial<PerspectiveCamera> | Partial<OrthographicCamera>
orthographic?: boolean
// Scene
shadows?: boolean | Partial<WebGLShadowMap>
raycaster?: Partial<Raycaster>
// Events
events?: EventManager
eventSource?: HTMLElement | React.RefObject<HTMLElement>
eventPrefix?: 'offset' | 'client' | 'page' | 'layer' | 'screen'
// Size
resize?: { scroll?: boolean; debounce?: number | { scroll: number; resize: number } }
// Performance
performance?: {
current?: number
min?: number
max?: number
debounce?: number
}
// Callbacks
onCreated?: (state: RootState) => void
onPointerMissed?: (event: MouseEvent) => void
}Examples
// Basic setup
<Canvas>
<Scene />
</Canvas>
// Custom camera
<Canvas camera={{ position: [0, 0, 5], fov: 75, near: 0.1, far: 1000 }}>
<Scene />
</Canvas>
// Orthographic camera
<Canvas orthographic camera={{ zoom: 50, position: [0, 0, 5] }}>
<Scene />
</Canvas>
// Enable shadows
<Canvas shadows>
<Scene />
</Canvas>
// Custom renderer settings
<Canvas
gl={{
antialias: true,
alpha: true,
powerPreference: 'high-performance'
}}
dpr={[1, 2]}
>
<Scene />
</Canvas>
// On-demand rendering
<Canvas frameloop="demand">
<Scene />
</Canvas>
// Performance monitoring
<Canvas
performance={{ min: 0.5, max: 1, debounce: 200 }}
onCreated={(state) => console.log('Canvas created:', state)}
>
<Scene />
</Canvas>---
Hooks
useFrame
Execute code on every rendered frame.
useFrame(
callback: (state: RootState, delta: number, xrFrame?: XRFrame) => void,
renderPriority?: number
): voidParameters:
callback- Function called every framerenderPriority- Execution order (default: 0, higher = later)
State Object:
interface RootState {
gl: WebGLRenderer
scene: Scene
camera: Camera
raycaster: Raycaster
pointer: Vector2
mouse: Vector2 // Deprecated, use pointer
clock: Clock
size: { width: number; height: number; top: number; left: number }
viewport: {
width: number
height: number
initialDpr: number
dpr: number
factor: number
distance: number
aspect: number
}
performance: { current: number; min: number; max: number; debounce: number }
frameloop: 'always' | 'demand' | 'never'
controls: any
invalidate: (frames?: number) => void
advance: (timestamp: number, runGlobalEffects?: boolean) => void
setSize: (width: number, height: number) => void
setDpr: (dpr: number) => void
setFrameloop: (frameloop: 'always' | 'demand' | 'never') => void
get: () => RootState
set: (partial: Partial<RootState>) => void
}Examples:
// Basic animation
function RotatingBox() {
const meshRef = useRef()
useFrame((state, delta) => {
meshRef.current.rotation.x += delta
meshRef.current.rotation.y += delta * 0.5
})
return <mesh ref={meshRef}>...</mesh>
}
// Access clock for time-based animations
function FloatingBox() {
const meshRef = useRef()
useFrame((state) => {
const time = state.clock.elapsedTime
meshRef.current.position.y = Math.sin(time) * 2
})
return <mesh ref={meshRef}>...</mesh>
}
// Control render loop
function CustomRender() {
useFrame(({ gl, scene, camera }) => {
gl.render(scene, camera)
}, 1) // renderPriority = 1 (takes over rendering)
}
// Ordered execution
function First() {
useFrame(() => console.log('First'), -1)
}
function Second() {
useFrame(() => console.log('Second'), 0)
}
function Third() {
useFrame(() => console.log('Third'), 1)
}---
useThree
Access R3F state and scene objects.
useThree<T = RootState>(
selector?: (state: RootState) => T,
equalityFn?: (a: T, b: T) => boolean
): TParameters:
selector- Function to select specific state (optional)equalityFn- Custom equality function for optimization
Examples:
// Get all state (re-renders on any change)
function Component() {
const state = useThree()
const { gl, scene, camera, size } = state
return null
}
// Selective subscription (only re-renders when size changes)
function Component() {
const size = useThree((state) => state.size)
console.log(size.width, size.height)
return null
}
// Multiple selections
function Component() {
const camera = useThree((state) => state.camera)
const viewport = useThree((state) => state.viewport)
const gl = useThree((state) => state.gl)
return null
}
// Get state non-reactively
function Component() {
const get = useThree((state) => state.get)
function handleClick() {
const freshState = get()
console.log(freshState.camera.position)
}
return <mesh onClick={handleClick}>...</mesh>
}
// Manual invalidation (trigger render)
function Component() {
const invalidate = useThree((state) => state.invalidate)
return (
<mesh onClick={() => invalidate()}>
<boxGeometry />
<meshStandardMaterial />
</mesh>
)
}
// Set frame loop
function Component() {
const setFrameloop = useThree((state) => state.setFrameloop)
useEffect(() => {
setFrameloop('demand') // Switch to on-demand rendering
}, [])
return null
}---
useLoader
Load assets with automatic caching and Suspense integration.
useLoader<T>(
loader: LoaderConstructor<T>,
url: string | string[],
extensions?: (loader: LoaderProto<T>) => void,
onProgress?: (event: ProgressEvent) => void
): T | T[]
// Static methods
useLoader.preload<T>(
loader: LoaderConstructor<T>,
url: string | string[],
extensions?: (loader: LoaderProto<T>) => void
): void
useLoader.clear<T>(
loader: LoaderConstructor<T>,
url: string | string[]
): voidExamples:
import { useLoader } from '@react-three/fiber'
import { TextureLoader, GLTFLoader } from 'three'
// Load texture
function TexturedBox() {
const texture = useLoader(TextureLoader, '/texture.jpg')
return (
<mesh>
<boxGeometry />
<meshStandardMaterial map={texture} />
</mesh>
)
}
// Load GLTF model
function Model() {
const gltf = useLoader(GLTFLoader, '/model.glb')
return <primitive object={gltf.scene} />
}
// Load multiple assets
function Scene() {
const [texture1, texture2, texture3] = useLoader(TextureLoader, [
'/tex1.jpg',
'/tex2.jpg',
'/tex3.jpg'
])
return (
<>
<mesh><meshStandardMaterial map={texture1} /></mesh>
<mesh><meshStandardMaterial map={texture2} /></mesh>
<mesh><meshStandardMaterial map={texture3} /></mesh>
</>
)
}
// Loader extensions (e.g., DRACO compression)
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader'
function CompressedModel() {
const gltf = useLoader(
GLTFLoader,
'/compressed.glb',
(loader) => {
const dracoLoader = new DRACOLoader()
dracoLoader.setDecoderPath('/draco/')
loader.setDRACOLoader(dracoLoader)
}
)
return <primitive object={gltf.scene} />
}
// Progress tracking
function ModelWithProgress() {
const [progress, setProgress] = useState(0)
const gltf = useLoader(
GLTFLoader,
'/large-model.glb',
undefined,
(event) => {
setProgress((event.loaded / event.total) * 100)
}
)
return <primitive object={gltf.scene} />
}
// Pre-loading
function Preloader() {
useEffect(() => {
useLoader.preload(GLTFLoader, '/model.glb')
useLoader.preload(TextureLoader, '/texture.jpg')
}, [])
return null
}
// Clear cache
function Component() {
useEffect(() => {
return () => {
useLoader.clear(GLTFLoader, '/model.glb')
}
}, [])
}---
useGraph
Access GLTF scene graph with typed nodes and materials.
useGraph(object: Object3D): {
nodes: { [name: string]: Object3D }
materials: { [name: string]: Material }
}Example:
import { useLoader } from '@react-three/fiber'
import { useGraph } from '@react-three/fiber'
import { GLTFLoader } from 'three'
function Model() {
const gltf = useLoader(GLTFLoader, '/model.glb')
const { nodes, materials } = useGraph(gltf.scene)
return (
<group>
<mesh geometry={nodes.Mesh.geometry} material={materials.Material} />
<mesh geometry={nodes.OtherMesh.geometry}>
<meshStandardMaterial color="red" />
</mesh>
</group>
)
}---
Events
R3F supports pointer events on any Object3D.
Supported Events
// Pointer events
onPointerOver?: (event: ThreeEvent<PointerEvent>) => void
onPointerOut?: (event: ThreeEvent<PointerEvent>) => void
onPointerEnter?: (event: ThreeEvent<PointerEvent>) => void
onPointerLeave?: (event: ThreeEvent<PointerEvent>) => void
onPointerMove?: (event: ThreeEvent<PointerEvent>) => void
onPointerDown?: (event: ThreeEvent<PointerEvent>) => void
onPointerUp?: (event: ThreeEvent<PointerEvent>) => void
onPointerCancel?: (event: ThreeEvent<PointerEvent>) => void
onPointerMissed?: (event: MouseEvent) => void
// Click events
onClick?: (event: ThreeEvent<MouseEvent>) => void
onContextMenu?: (event: ThreeEvent<MouseEvent>) => void
onDoubleClick?: (event: ThreeEvent<MouseEvent>) => void
// Wheel event
onWheel?: (event: ThreeEvent<WheelEvent>) => voidThreeEvent Object
interface ThreeEvent<T> extends Omit<T, 'target'> {
// Three.js specific
intersections: Intersection[]
object: Object3D
eventObject: Object3D
unprojectedPoint: Vector3
ray: Ray
camera: Camera
sourceEvent: T
delta: number
// Helpers
stopPropagation: () => void
nativeEvent: T
pointer: Vector2
pointerId: number
distance: number
point: Vector3
uv: Vector2
face: Face | null
faceIndex: number | null
}Examples
// Basic click handler
<mesh onClick={(e) => console.log('Clicked!', e.point)}>
<boxGeometry />
<meshStandardMaterial />
</mesh>
// Hover states
function InteractiveBox() {
const [hovered, setHovered] = useState(false)
return (
<mesh
onPointerOver={(e) => {
e.stopPropagation()
setHovered(true)
document.body.style.cursor = 'pointer'
}}
onPointerOut={(e) => {
setHovered(false)
document.body.style.cursor = 'auto'
}}
>
<boxGeometry />
<meshStandardMaterial color={hovered ? 'hotpink' : 'orange'} />
</mesh>
)
}
// Stop event propagation
<group onClick={(e) => e.stopPropagation()}>
<mesh onClick={() => console.log('Mesh clicked')} />
<mesh onClick={() => console.log('This will also fire without stopPropagation')} />
</group>
// Access intersection data
<mesh
onClick={(e) => {
console.log('Hit point:', e.point)
console.log('Hit face:', e.face)
console.log('UV coordinates:', e.uv)
console.log('Distance from camera:', e.distance)
console.log('All intersections:', e.intersections)
}}
>
<sphereGeometry />
<meshStandardMaterial />
</mesh>
// Pointer missed (clicked on empty space)
<Canvas onPointerMissed={() => console.log('Clicked on background')}>
<mesh />
</Canvas>---
Three.js Object Props
R3F translates JSX props to Three.js object properties.
Prop Mapping
// Array notation → .set()
<mesh position={[1, 2, 3]} /> // mesh.position.set(1, 2, 3)
<mesh rotation={[0, Math.PI, 0]} /> // mesh.rotation.set(0, Math.PI, 0)
<mesh scale={[2, 2, 2]} /> // mesh.scale.set(2, 2, 2)
// Dash notation (axis-specific)
<mesh position-x={1} position-y={2} position-z={3} />
<mesh scale-x={2} scale-y={1} />
// Direct property assignment
<mesh visible={false} /> // mesh.visible = false
<mesh castShadow receiveShadow /> // mesh.castShadow = true, mesh.receiveShadow = true
// Constructor arguments
<boxGeometry args={[1, 1, 1]} /> // new BoxGeometry(1, 1, 1)
<meshStandardMaterial args={[{ color: 'red' }]} /> // new MeshStandardMaterial({ color: 'red' })
// Attach to specific parent property
<mesh>
<meshStandardMaterial attach="material" /> // mesh.material = material
</mesh>
// Nested properties
<meshStandardMaterial color="red" roughness={0.5} metalness={0.8} />
// Set (for Vector-like properties)
<pointLight position={[10, 10, 10]} />Special Props
// attach: Attach to parent property
<mesh>
<meshStandardMaterial attach="material" />
<boxGeometry attach="geometry" />
</mesh>
// attach-array: Attach to array index
<group>
<mesh attach="children-0" />
<mesh attach="children-1" />
</group>
// dispose: Control automatic disposal
<mesh dispose={null}> {/* Never dispose */}
<boxGeometry />
<meshStandardMaterial />
</mesh>
// args: Constructor arguments
<sphereGeometry args={[1, 32, 32]} /> // radius, widthSegments, heightSegments
// object: Pass pre-existing Three.js object
<primitive object={myThreeJsObject} />
// ref: Get reference to underlying Three.js object
<mesh ref={meshRef} />---
Drei Helpers
Essential Drei components and hooks.
OrbitControls
interface OrbitControlsProps {
makeDefault?: boolean
camera?: Camera
domElement?: HTMLElement
target?: Vector3
enableDamping?: boolean
dampingFactor?: number
enableZoom?: boolean
enableRotate?: boolean
enablePan?: boolean
minDistance?: number
maxDistance?: number
minPolarAngle?: number
maxPolarAngle?: number
onChange?: (e?: Event) => void
onStart?: (e?: Event) => void
onEnd?: (e?: Event) => void
}import { OrbitControls } from '@react-three/drei'
<OrbitControls
makeDefault
enableDamping
dampingFactor={0.05}
minDistance={3}
maxDistance={20}
maxPolarAngle={Math.PI / 2}
target={[0, 1, 0]}
/>Environment
import { Environment } from '@react-three/drei'
// Preset HDRI
<Environment preset="sunset" background />
// Custom HDRI
<Environment files="/hdri.hdr" />
// Ground reflection
<Environment preset="city" ground={{ height: 15, radius: 60, scale: 100 }} />useGLTF
import { useGLTF } from '@react-three/drei'
function Model() {
const { scene, nodes, materials } = useGLTF('/model.glb')
return <primitive object={scene} />
}
// Pre-load
useGLTF.preload('/model.glb')Text & Text3D
import { Text, Text3D } from '@react-three/drei'
// 2D billboard text
<Text
position={[0, 2, 0]}
fontSize={1}
color="white"
anchorX="center"
anchorY="middle"
maxWidth={5}
lineHeight={1}
letterSpacing={0.02}
textAlign="center"
font="/fonts/font.woff"
outlineWidth={0.1}
outlineColor="#000000"
>
Hello World
</Text>
// 3D extruded text
<Text3D
font="/fonts/helvetiker_regular.typeface.json"
size={1}
height={0.2}
curveSegments={12}
bevelEnabled
bevelThickness={0.02}
bevelSize={0.02}
bevelOffset={0}
bevelSegments={5}
>
3D Text
<meshNormalMaterial />
</Text3D>Center & Bounds
import { Center, Bounds, useBounds } from '@react-three/drei'
// Auto-center
<Center>
<Model />
</Center>
// Auto-fit camera
<Bounds fit clip observe margin={1.2}>
<Model />
</Bounds>
// Manual control
function SelectToZoom() {
const bounds = useBounds()
return (
<mesh onClick={(e) => {
e.stopPropagation()
bounds.refresh(e.object).fit()
}}>
<boxGeometry />
<meshStandardMaterial />
</mesh>
)
}Html
import { Html } from '@react-three/drei'
<mesh>
<boxGeometry />
<meshStandardMaterial />
<Html
position={[0, 1, 0]}
center
distanceFactor={10}
occlude
transform
sprite
>
<div className="annotation">Label</div>
</Html>
</mesh>ScrollControls
import { ScrollControls, Scroll, useScroll } from '@react-three/drei'
function Scene() {
const scroll = useScroll()
const meshRef = useRef()
useFrame(() => {
const offset = scroll.offset // 0-1
meshRef.current.position.y = offset * 10
})
return <mesh ref={meshRef}>...</mesh>
}
<Canvas>
<ScrollControls pages={3} damping={0.5}>
<Scroll>
<Scene />
</Scroll>
<Scroll html>
<h1>HTML Content</h1>
</Scroll>
</ScrollControls>
</Canvas>ContactShadows
import { ContactShadows } from '@react-three/drei'
<ContactShadows
position={[0, -0.8, 0]}
opacity={0.5}
scale={10}
blur={1}
far={10}
resolution={256}
color="#000000"
/>Sky
import { Sky } from '@react-three/drei'
<Sky
distance={450000}
sunPosition={[0, 1, 0]}
inclination={0}
azimuth={0.25}
rayleigh={2}
turbidity={10}
mieCoefficient={0.005}
mieDirectionalG={0.8}
/>Stars
import { Stars } from '@react-three/drei'
<Stars
radius={100}
depth={50}
count={5000}
factor={4}
saturation={0}
fade
speed={1}
/>---
Performance Helpers (Drei)
AdaptiveDpr
import { AdaptiveDpr } from '@react-three/drei'
<AdaptiveDpr pixelated />AdaptiveEvents
import { AdaptiveEvents } from '@react-three/drei'
<AdaptiveEvents />PerformanceMonitor
import { PerformanceMonitor } from '@react-three/drei'
<PerformanceMonitor
onIncline={() => console.log('Performance improved')}
onDecline={() => console.log('Performance degraded')}
onFallback={() => console.log('Fallback triggered')}
onChange={({ factor }) => console.log('Factor:', factor)}
flipflops={3}
bounds={(refreshRate) => [50, 90]}
>
<Scene />
</PerformanceMonitor>Preload
import { Preload } from '@react-three/drei'
<Canvas>
<Scene />
<Preload all /> {/* Preload all assets */}
</Canvas>---
Resources
#!/usr/bin/env python3
"""
React Three Fiber Component Generator
======================================
Generates R3F component boilerplate for common patterns with TypeScript support.
Usage:
python3 component_generator.py --type box --name MyBox --output MyBox.jsx
python3 component_generator.py --type model --name Scene --props "modelPath,scale" --typescript
python3 component_generator.py --type interactive --name Button --events "onClick,onPointerOver"
python3 component_generator.py --type animated --name FloatingCube --animation "rotation,position"
Component Types:
- box: Basic mesh with geometry and material
- sphere: Sphere mesh component
- model: GLTF model loader with Suspense
- interactive: Interactive mesh with pointer events
- animated: Animated component with useFrame
- group: Group container with multiple children
- instanced: Instanced mesh for performance
- camera-rig: Camera control component
- lighting: Lighting setup component
- environment: Environment with Drei helpers
- scene: Complete scene setup
- custom: Custom component template
Options:
--type: Component type (required)
--name: Component name (default: Component)
--output: Output file path (default: stdout)
--typescript: Generate TypeScript component
--props: Comma-separated list of props
--events: Comma-separated list of event handlers
--animation: Comma-separated list of animation targets (rotation, position, scale)
--drei: Include Drei helper imports
--framework: Target framework (vanilla, nextjs, vite)
"""
import argparse
import sys
from typing import List, Dict, Optional
class R3FComponentGenerator:
"""Generate React Three Fiber component boilerplate."""
def __init__(
self,
component_type: str,
name: str = "Component",
typescript: bool = False,
props: Optional[List[str]] = None,
events: Optional[List[str]] = None,
animation: Optional[List[str]] = None,
drei: bool = False,
framework: str = "vanilla"
):
self.component_type = component_type
self.name = name
self.typescript = typescript
self.props = props or []
self.events = events or []
self.animation = animation or []
self.drei = drei
self.framework = framework
def generate(self) -> str:
"""Generate component code."""
generators = {
'box': self._generate_box,
'sphere': self._generate_sphere,
'model': self._generate_model,
'interactive': self._generate_interactive,
'animated': self._generate_animated,
'group': self._generate_group,
'instanced': self._generate_instanced,
'camera-rig': self._generate_camera_rig,
'lighting': self._generate_lighting,
'environment': self._generate_environment,
'scene': self._generate_scene,
'custom': self._generate_custom,
}
generator = generators.get(self.component_type)
if not generator:
raise ValueError(f"Unknown component type: {self.component_type}")
return generator()
def _get_imports(self, additional: List[str] = None) -> str:
"""Generate import statements."""
imports = ["import { useRef } from 'react'"]
r3f_imports = ["useFrame", "useThree"]
if additional:
r3f_imports.extend(additional)
imports.append(f"import {{ {', '.join(set(r3f_imports))} }} from '@react-three/fiber'")
if self.drei:
drei_imports = ["OrbitControls", "Environment", "useGLTF"]
imports.append(f"import {{ {', '.join(drei_imports)} }} from '@react-three/drei'")
if self.typescript:
imports.append("import * as THREE from 'three'")
return "\n".join(imports)
def _get_props_interface(self) -> str:
"""Generate TypeScript props interface."""
if not self.typescript or not self.props:
return ""
props_lines = []
for prop in self.props:
# Infer types from common prop names
if prop in ['position', 'rotation', 'scale']:
props_lines.append(f" {prop}?: [number, number, number]")
elif prop in ['color', 'modelPath', 'name']:
props_lines.append(f" {prop}?: string")
elif prop in ['visible', 'castShadow', 'receiveShadow']:
props_lines.append(f" {prop}?: boolean")
elif prop.endswith('Ref'):
props_lines.append(f" {prop}?: React.RefObject<THREE.Mesh>")
else:
props_lines.append(f" {prop}?: any")
return f"\ninterface {self.name}Props {{\n" + "\n".join(props_lines) + "\n}\n"
def _get_props_signature(self) -> str:
"""Generate props parameter signature."""
if not self.props:
return "()"
if self.typescript:
return f"({{ {', '.join(self.props)} }}: {self.name}Props)"
else:
return f"({{ {', '.join(self.props)} }})"
def _get_event_handlers(self) -> str:
"""Generate event handler props."""
if not self.events:
return ""
handlers = []
for event in self.events:
if event.startswith('on'):
handler_name = event[2:].lower()
handlers.append(f'{event}={(e) => console.log("{handler_name}", e)}')
else:
handlers.append(f'on{event.capitalize()}={(e) => console.log("{event}", e)}')
return "\n ".join(handlers)
def _generate_box(self) -> str:
"""Generate basic box component."""
imports = self._get_imports()
props_interface = self._get_props_interface()
props_sig = self._get_props_signature()
default_props = ["position = [0, 0, 0]", "color = 'orange'"]
props_str = ', '.join(default_props + self.props)
return f"""{imports}
{props_interface}
export function {self.name}{props_sig} {{
return (
<mesh position={{position}}>
<boxGeometry args={{[1, 1, 1]}} />
<meshStandardMaterial color={{color}} />
</mesh>
)
}}
"""
def _generate_sphere(self) -> str:
"""Generate sphere component."""
imports = self._get_imports()
props_interface = self._get_props_interface()
props_sig = self._get_props_signature()
return f"""{imports}
{props_interface}
export function {self.name}{props_sig} {{
const meshRef = useRef{f'<THREE.Mesh>(null)' if self.typescript else '()'}
return (
<mesh ref={{meshRef}}>
<sphereGeometry args={{[1, 32, 32]}} />
<meshStandardMaterial color="hotpink" />
</mesh>
)
}}
"""
def _generate_model(self) -> str:
"""Generate GLTF model loader component."""
imports = ["import { Suspense } from 'react'"]
imports.append("import { useGLTF } from '@react-three/drei'")
if self.typescript:
imports.append("import { GLTF } from 'three-stdlib'")
props_interface = ""
if self.typescript:
props_interface = f"""
interface {self.name}Props {{
modelPath: string
position?: [number, number, number]
scale?: number | [number, number, number]
}}
"""
return f"""{chr(10).join(imports)}
{props_interface}
function Model({{ modelPath, position = [0, 0, 0], scale = 1 }}{f': {self.name}Props' if self.typescript else ''}) {{
const {{ scene }} = useGLTF(modelPath)
return <primitive object={{scene}} position={{position}} scale={{scale}} />
}}
// Preload the model
useGLTF.preload('/path/to/model.glb')
export function {self.name}({{ modelPath = '/model.glb', ...props }}{f': {self.name}Props' if self.typescript else ''}) {{
return (
<Suspense fallback={{null}}>
<Model modelPath={{modelPath}} {{...props}} />
</Suspense>
)
}}
"""
def _generate_interactive(self) -> str:
"""Generate interactive component with pointer events."""
imports = self._get_imports() + "\nimport { useState } from 'react'"
props_interface = self._get_props_interface()
props_sig = self._get_props_signature()
event_handlers = self._get_event_handlers() or """onClick={(e) => {
e.stopPropagation()
setActive(!active)
}}
onPointerOver={(e) => {
e.stopPropagation()
setHovered(true)
}}
onPointerOut={(e) => setHovered(false)}"""
return f"""{imports}
{props_interface}
export function {self.name}{props_sig} {{
const [hovered, setHovered] = useState(false)
const [active, setActive] = useState(false)
return (
<mesh
scale={{active ? 1.5 : 1}}
{event_handlers}>
<boxGeometry args={{[1, 1, 1]}} />
<meshStandardMaterial color={{hovered ? 'hotpink' : 'orange'}} />
</mesh>
)
}}
"""
def _generate_animated(self) -> str:
"""Generate animated component with useFrame."""
imports = self._get_imports()
props_interface = self._get_props_interface()
props_sig = self._get_props_signature()
# Generate animation code based on targets
animation_code = []
if 'rotation' in self.animation or not self.animation:
animation_code.append("meshRef.current.rotation.x += delta")
animation_code.append("meshRef.current.rotation.y += delta * 0.5")
if 'position' in self.animation:
animation_code.append("meshRef.current.position.y = Math.sin(state.clock.elapsedTime) * 2")
if 'scale' in self.animation:
animation_code.append("meshRef.current.scale.x = 1 + Math.sin(state.clock.elapsedTime) * 0.3")
if not animation_code:
animation_code = ["// Add your animation logic here"]
return f"""{imports}
{props_interface}
export function {self.name}{props_sig} {{
const meshRef = useRef{f'<THREE.Mesh>(null!)' if self.typescript else '()'}
useFrame((state, delta) => {{
{chr(10).join(f' {line}' for line in animation_code)}
}})
return (
<mesh ref={{meshRef}}>
<boxGeometry args={{[1, 1, 1]}} />
<meshStandardMaterial color="orange" />
</mesh>
)
}}
"""
def _generate_group(self) -> str:
"""Generate group component."""
imports = self._get_imports()
props_interface = self._get_props_interface()
props_sig = self._get_props_signature()
return f"""{imports}
{props_interface}
export function {self.name}{props_sig} {{
const groupRef = useRef{f'<THREE.Group>(null!)' if self.typescript else '()'}
useFrame((state, delta) => {{
groupRef.current.rotation.y += delta * 0.5
}})
return (
<group ref={{groupRef}}>
<mesh position={{[-2, 0, 0]}}>
<boxGeometry />
<meshStandardMaterial color="orange" />
</mesh>
<mesh position={{[2, 0, 0]}}>
<sphereGeometry />
<meshStandardMaterial color="hotpink" />
</mesh>
</group>
)
}}
"""
def _generate_instanced(self) -> str:
"""Generate instanced mesh component for performance."""
imports = ["import { useRef, useMemo } from 'react'"]
imports.append("import { useFrame } from '@react-three/fiber'")
if self.typescript:
imports.append("import * as THREE from 'three'")
return f"""{chr(10).join(imports)}
export function {self.name}({{ count = 1000 }}) {{
const meshRef = useRef{f'<THREE.InstancedMesh>(null!)' if self.typescript else '()'}
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() * 40 - 20
const y = Math.random() * 40 - 20
const z = Math.random() * 40 - 20
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.rotation.set(s * 5, s * 5, s * 5)
dummy.updateMatrix()
meshRef.current.setMatrixAt(i, dummy.matrix)
}})
meshRef.current.instanceMatrix.needsUpdate = true
}})
return (
<instancedMesh ref={{meshRef}} args={{[undefined, undefined, count]}}>
<dodecahedronGeometry args={{[0.2, 0]}} />
<meshPhongMaterial color="#ff4040" />
</instancedMesh>
)
}}
"""
def _generate_camera_rig(self) -> str:
"""Generate camera rig component."""
imports = ["import { useRef } from 'react'"]
imports.append("import { useFrame } from '@react-three/fiber'")
if self.typescript:
imports.append("import * as THREE from 'three'")
return f"""{chr(10).join(imports)}
export function {self.name}({{ children }}) {{
const groupRef = useRef{f'<THREE.Group>(null!)' if self.typescript else '()'}
useFrame((state) => {{
// Smooth camera follow
groupRef.current.position.lerp(
new THREE.Vector3(
state.mouse.x * 2,
state.mouse.y * 2,
10
),
0.05
)
// Look at center
groupRef.current.lookAt(0, 0, 0)
}})
return (
<group ref={{groupRef}}>
{{children}}
</group>
)
}}
"""
def _generate_lighting(self) -> str:
"""Generate lighting setup component."""
return f"""import {{ useRef }} from 'react'
import {{ useFrame }} from '@react-three/fiber'
export function {self.name}() {{
const lightRef = useRef()
useFrame((state) => {{
const time = state.clock.elapsedTime
lightRef.current.position.x = Math.sin(time) * 5
lightRef.current.position.z = Math.cos(time) * 5
}})
return (
<>
<ambientLight intensity={{0.5}} />
<directionalLight
ref={{lightRef}}
position={{[5, 5, 5]}}
intensity={{1}}
castShadow
shadow-mapSize={{[1024, 1024]}}
shadow-camera-far={{50}}
shadow-camera-left={{-10}}
shadow-camera-right={{10}}
shadow-camera-top={{10}}
shadow-camera-bottom={{-10}}
/>
<pointLight position={{[-10, -10, -10]}} intensity={{0.5}} />
<hemisphereLight intensity={{0.35}} groundColor="black" />
</>
)
}}
"""
def _generate_environment(self) -> str:
"""Generate environment component with Drei helpers."""
return f"""import {{ Environment, ContactShadows, Sky }} from '@react-three/drei'
export function {self.name}() {{
return (
<>
<Environment preset="sunset" />
<ContactShadows
position={{[0, -1.4, 0]}}
opacity={{0.75}}
scale={{10}}
blur={{2.5}}
far={{4}}
/>
<Sky sunPosition={{[100, 20, 100]}} />
</>
)
}}
"""
def _generate_scene(self) -> str:
"""Generate complete scene setup."""
return f"""import {{ Canvas }} from '@react-three/fiber'
import {{ OrbitControls, Environment, ContactShadows }} from '@react-three/drei'
function Scene() {{
return (
<>
{/* Lighting */}
<ambientLight intensity={{0.5}} />
<directionalLight position={{[5, 5, 5]}} intensity={{1}} castShadow />
{/* 3D Objects */}
<mesh position={{[0, 1, 0]}} castShadow>
<boxGeometry args={{[1, 1, 1]}} />
<meshStandardMaterial color="orange" />
</mesh>
{/* Ground */}
<mesh rotation={{[-Math.PI / 2, 0, 0]}} position={{[0, 0, 0]}} receiveShadow>
<planeGeometry args={{[10, 10]}} />
<meshStandardMaterial color="#808080" />
</mesh>
{/* Environment */}
<Environment preset="city" />
<ContactShadows position={{[0, 0, 0]}} opacity={{0.5}} scale={{10}} blur={{1}} far={{10}} />
{/* Camera Controls */}
<OrbitControls makeDefault />
</>
)
}}
export function {self.name}() {{
return (
<Canvas shadows camera={{{{ position: [5, 5, 5], fov: 50 }}}}>
<Scene />
</Canvas>
)
}}
"""
def _generate_custom(self) -> str:
"""Generate custom component template."""
imports = self._get_imports()
props_interface = self._get_props_interface()
props_sig = self._get_props_signature()
return f"""{imports}
{props_interface}
export function {self.name}{props_sig} {{
const meshRef = useRef{f'<THREE.Mesh>(null!)' if self.typescript else '()'}
// Add your custom logic here
useFrame((state, delta) => {{
// Animation logic
}})
return (
<mesh ref={{meshRef}}>
<boxGeometry args={{[1, 1, 1]}} />
<meshStandardMaterial color="orange" />
</mesh>
)
}}
"""
def main():
parser = argparse.ArgumentParser(
description='Generate React Three Fiber component boilerplate',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog=__doc__
)
parser.add_argument(
'--type',
required=True,
choices=['box', 'sphere', 'model', 'interactive', 'animated', 'group',
'instanced', 'camera-rig', 'lighting', 'environment', 'scene', 'custom'],
help='Component type to generate'
)
parser.add_argument(
'--name',
default='Component',
help='Component name (default: Component)'
)
parser.add_argument(
'--output',
help='Output file path (default: stdout)'
)
parser.add_argument(
'--typescript',
action='store_true',
help='Generate TypeScript component'
)
parser.add_argument(
'--props',
help='Comma-separated list of props'
)
parser.add_argument(
'--events',
help='Comma-separated list of event handlers (e.g., onClick,onPointerOver)'
)
parser.add_argument(
'--animation',
help='Comma-separated list of animation targets (rotation,position,scale)'
)
parser.add_argument(
'--drei',
action='store_true',
help='Include Drei helper imports'
)
parser.add_argument(
'--framework',
default='vanilla',
choices=['vanilla', 'nextjs', 'vite'],
help='Target framework (default: vanilla)'
)
args = parser.parse_args()
# Parse comma-separated lists
props = args.props.split(',') if args.props else []
events = args.events.split(',') if args.events else []
animation = args.animation.split(',') if args.animation else []
# Generate component
generator = R3FComponentGenerator(
component_type=args.type,
name=args.name,
typescript=args.typescript,
props=props,
events=events,
animation=animation,
drei=args.drei,
framework=args.framework
)
try:
code = generator.generate()
# Output
if args.output:
with open(args.output, 'w') as f:
f.write(code)
print(f"✅ Generated {args.name} component → {args.output}")
else:
print(code)
except Exception as e:
print(f"❌ Error: {e}", file=sys.stderr)
sys.exit(1)
if __name__ == '__main__':
main()
#!/usr/bin/env python3
"""
React Three Fiber Scene Setup Tool
===================================
Interactive CLI tool to generate complete R3F scene boilerplate with common patterns.
Usage:
python3 scene_setup.py
python3 scene_setup.py --preset standard --output src/Scene.jsx
python3 scene_setup.py --preset performance --typescript
python3 scene_setup.py --interactive
Presets:
- minimal: Basic scene with lighting and controls
- standard: Complete scene with environment, shadows, controls
- performance: Optimized scene with adaptive performance
- creative: Artistic scene with post-processing effects
- product: Product viewer setup with proper lighting
- game: Game-ready scene with physics
Features:
- Lighting configurations (ambient, directional, point, hemisphere, HDRI)
- Camera setups (perspective, orthographic)
- Controls (OrbitControls, FlyControls, PointerLockControls)
- Environment (Drei presets, HDRI, Sky)
- Shadows (PCF, VSM, contact shadows)
- Post-processing (bloom, DOF, SSAO, chromatic aberration)
- Performance optimizations (adaptive DPR, LOD, instancing)
- Physics integration (Rapier, Cannon)
"""
import argparse
import sys
from typing import Dict, List, Optional
class R3FSceneSetup:
"""Generate React Three Fiber scene setup."""
PRESETS = {
'minimal': {
'lighting': ['ambient', 'directional'],
'controls': ['orbit'],
'environment': None,
'shadows': False,
'post_processing': False,
'performance': False,
},
'standard': {
'lighting': ['ambient', 'directional', 'hemisphere'],
'controls': ['orbit'],
'environment': 'drei-preset',
'shadows': True,
'post_processing': False,
'performance': False,
},
'performance': {
'lighting': ['ambient', 'directional'],
'controls': ['orbit'],
'environment': None,
'shadows': False,
'post_processing': False,
'performance': True,
},
'creative': {
'lighting': ['ambient', 'directional', 'point'],
'controls': ['orbit'],
'environment': 'drei-preset',
'shadows': True,
'post_processing': True,
'performance': False,
},
'product': {
'lighting': ['ambient', 'directional', 'point', 'hemisphere'],
'controls': ['orbit'],
'environment': 'drei-preset',
'shadows': True,
'post_processing': False,
'performance': False,
},
'game': {
'lighting': ['ambient', 'directional'],
'controls': ['pointer-lock'],
'environment': None,
'shadows': True,
'post_processing': False,
'performance': True,
'physics': True,
},
}
def __init__(
self,
preset: str = 'standard',
typescript: bool = False,
lighting: Optional[List[str]] = None,
controls: Optional[str] = None,
environment: Optional[str] = None,
shadows: bool = False,
post_processing: bool = False,
performance: bool = False,
physics: bool = False,
):
preset_config = self.PRESETS.get(preset, self.PRESETS['standard'])
self.typescript = typescript
self.lighting = lighting or preset_config['lighting']
self.controls = controls or preset_config['controls'][0]
self.environment = environment if environment is not None else preset_config['environment']
self.shadows = shadows or preset_config['shadows']
self.post_processing = post_processing or preset_config.get('post_processing', False)
self.performance = performance or preset_config.get('performance', False)
self.physics = physics or preset_config.get('physics', False)
def generate(self) -> str:
"""Generate complete scene code."""
imports = self._generate_imports()
lighting_component = self._generate_lighting()
environment_component = self._generate_environment()
controls_component = self._generate_controls()
post_processing_component = self._generate_post_processing()
performance_component = self._generate_performance()
physics_component = self._generate_physics()
scene_component = self._generate_scene_component()
canvas_wrapper = self._generate_canvas_wrapper()
return f"""{imports}
{lighting_component}
{environment_component}
{controls_component}
{post_processing_component}
{performance_component}
{physics_component}
{scene_component}
{canvas_wrapper}
"""
def _generate_imports(self) -> str:
"""Generate import statements."""
imports = ["import { Canvas } from '@react-three/fiber'"]
drei_imports = []
if self.controls == 'orbit':
drei_imports.append('OrbitControls')
elif self.controls == 'fly':
drei_imports.append('FlyControls')
elif self.controls == 'pointer-lock':
drei_imports.append('PointerLockControls')
if self.environment:
drei_imports.append('Environment')
if self.shadows and self.environment == 'drei-preset':
drei_imports.append('ContactShadows')
if self.environment == 'sky':
drei_imports.append('Sky')
if self.performance:
drei_imports.extend(['AdaptiveDpr', 'AdaptiveEvents', 'PerformanceMonitor'])
if drei_imports:
imports.append(f"import {{ {', '.join(drei_imports)} }} from '@react-three/drei'")
if self.post_processing:
imports.append("import { EffectComposer, Bloom, DepthOfField } from '@react-three/postprocessing'")
if self.physics:
imports.append("import { Physics, RigidBody } from '@react-three/rapier'")
if self.typescript:
imports.append("import * as THREE from 'three'")
return "\n".join(imports)
def _generate_lighting(self) -> str:
"""Generate lighting setup component."""
lights = []
if 'ambient' in self.lighting:
lights.append(" <ambientLight intensity={0.5} />")
if 'directional' in self.lighting:
shadow_props = """
castShadow
shadow-mapSize={[1024, 1024]}
shadow-camera-far={50}
shadow-camera-left={-10}
shadow-camera-right={10}
shadow-camera-top={10}
shadow-camera-bottom={-10}""" if self.shadows else ""
lights.append(f""" <directionalLight
position={{[5, 5, 5]}}
intensity={{1}}{shadow_props}
/>""")
if 'point' in self.lighting:
lights.append(" <pointLight position={[-10, -10, -10]} intensity={0.5} />")
if 'hemisphere' in self.lighting:
lights.append(' <hemisphereLight intensity={0.35} groundColor="black" />')
if 'spot' in self.lighting:
lights.append(""" <spotLight
position={[10, 10, 10]}
angle={0.15}
penumbra={1}
intensity={1}
castShadow
/>""")
lighting_code = "\n".join(lights) if lights else " {/* Add your lighting here */}"
return f"""function Lighting() {{
return (
<>
{lighting_code}
</>
)
}}"""
def _generate_environment(self) -> str:
"""Generate environment setup."""
if not self.environment:
return "// No environment configured"
if self.environment == 'drei-preset':
shadows = ""
if self.shadows:
shadows = """
<ContactShadows
position={[0, -1.4, 0]}
opacity={0.75}
scale={10}
blur={2.5}
far={4}
/>"""
return f"""function EnvironmentSetup() {{
return (
<>
<Environment preset="sunset" />{shadows}
</>
)
}}"""
elif self.environment == 'sky':
return """function EnvironmentSetup() {
return <Sky sunPosition={[100, 20, 100]} />
}"""
elif self.environment == 'hdri':
return """function EnvironmentSetup() {
return <Environment files="/path/to/hdri.hdr" />
}"""
return "// Environment not configured"
def _generate_controls(self) -> str:
"""Generate camera controls."""
if self.controls == 'orbit':
return """function Controls() {
return <OrbitControls makeDefault />
}"""
elif self.controls == 'fly':
return """function Controls() {
return <FlyControls makeDefault />
}"""
elif self.controls == 'pointer-lock':
return """function Controls() {
return <PointerLockControls makeDefault />
}"""
else:
return "// No controls configured"
def _generate_post_processing(self) -> str:
"""Generate post-processing effects."""
if not self.post_processing:
return "// Post-processing disabled"
return """function Effects() {
return (
<EffectComposer>
<Bloom luminanceThreshold={0} luminanceSmoothing={0.9} height={300} />
<DepthOfField focusDistance={0} focalLength={0.02} bokehScale={2} height={480} />
</EffectComposer>
)
}"""
def _generate_performance(self) -> str:
"""Generate performance optimization components."""
if not self.performance:
return "// Performance optimizations disabled"
return """function PerformanceOptimizations() {
return (
<>
<AdaptiveDpr pixelated />
<AdaptiveEvents />
<PerformanceMonitor>
{/* Monitor performance and adjust quality */}
</PerformanceMonitor>
</>
)
}"""
def _generate_physics(self) -> str:
"""Generate physics setup."""
if not self.physics:
return "// Physics disabled"
return """function PhysicsWorld({ children }) {
return (
<Physics gravity={[0, -9.81, 0]}>
{children}
</Physics>
)
}"""
def _generate_scene_component(self) -> str:
"""Generate main scene component."""
components = []
components.append(" <Lighting />")
if self.environment:
components.append(" <EnvironmentSetup />")
if self.performance:
components.append(" <PerformanceOptimizations />")
components.append("""
{/* 3D Objects */}
<mesh position={[0, 1, 0]} castShadow>
<boxGeometry args={[1, 1, 1]} />
<meshStandardMaterial color="orange" />
</mesh>
{/* Ground */}
<mesh rotation={[-Math.PI / 2, 0, 0]} position={[0, 0, 0]} receiveShadow>
<planeGeometry args={[10, 10]} />
<meshStandardMaterial color="#808080" />
</mesh>""")
components.append("\n <Controls />")
if self.post_processing:
components.append(" <Effects />")
scene_content = "\n".join(components)
if self.physics:
return f"""function Scene() {{
return (
<PhysicsWorld>
{scene_content}
</PhysicsWorld>
)
}}"""
else:
return f"""function Scene() {{
return (
<>
{scene_content}
</>
)
}}"""
def _generate_canvas_wrapper(self) -> str:
"""Generate Canvas wrapper component."""
canvas_props = []
if self.shadows:
canvas_props.append("shadows")
canvas_props.append("camera={{ position: [5, 5, 5], fov: 50 }}")
if self.performance:
canvas_props.append("dpr={[1, 2]}")
canvas_props.append('frameloop="demand"')
props_str = " ".join(canvas_props)
return f"""export default function Experience() {{
return (
<Canvas {props_str}>
<Scene />
</Canvas>
)
}}"""
def interactive_mode():
"""Run interactive mode to configure scene."""
print("\n🎨 React Three Fiber Scene Setup - Interactive Mode\n")
print("=" * 60)
# Choose preset
print("\nChoose a preset:")
print(" 1. minimal - Basic scene with lighting and controls")
print(" 2. standard - Complete scene with environment, shadows, controls")
print(" 3. performance - Optimized scene with adaptive performance")
print(" 4. creative - Artistic scene with post-processing effects")
print(" 5. product - Product viewer setup with proper lighting")
print(" 6. game - Game-ready scene with physics")
print(" 7. custom - Configure manually")
preset_choice = input("\nSelect preset (1-7): ").strip()
preset_map = {
'1': 'minimal',
'2': 'standard',
'3': 'performance',
'4': 'creative',
'5': 'product',
'6': 'game',
}
if preset_choice == '7':
# Custom configuration
print("\n📝 Custom Configuration\n")
# Lighting
print("Lighting types (comma-separated):")
print(" ambient, directional, point, hemisphere, spot")
lighting_input = input("Select lighting: ").strip()
lighting = [l.strip() for l in lighting_input.split(',')] if lighting_input else ['ambient', 'directional']
# Controls
print("\nCamera controls:")
print(" 1. orbit - OrbitControls (mouse drag rotation)")
print(" 2. fly - FlyControls (keyboard navigation)")
print(" 3. pointer - PointerLockControls (FPS-style)")
controls_choice = input("Select controls (1-3): ").strip()
controls_map = {'1': 'orbit', '2': 'fly', '3': 'pointer'}
controls = controls_map.get(controls_choice, 'orbit')
# Environment
print("\nEnvironment:")
print(" 1. drei-preset - Use Drei environment preset")
print(" 2. sky - Sky component")
print(" 3. none - No environment")
env_choice = input("Select environment (1-3): ").strip()
env_map = {'1': 'drei-preset', '2': 'sky', '3': None}
environment = env_map.get(env_choice, None)
# Features
shadows = input("\nEnable shadows? (y/n): ").strip().lower() == 'y'
post_processing = input("Enable post-processing? (y/n): ").strip().lower() == 'y'
performance = input("Enable performance optimizations? (y/n): ").strip().lower() == 'y'
physics = input("Enable physics? (y/n): ").strip().lower() == 'y'
setup = R3FSceneSetup(
preset='standard',
lighting=lighting,
controls=controls,
environment=environment,
shadows=shadows,
post_processing=post_processing,
performance=performance,
physics=physics,
)
else:
preset = preset_map.get(preset_choice, 'standard')
setup = R3FSceneSetup(preset=preset)
# TypeScript
typescript = input("\nGenerate TypeScript? (y/n): ").strip().lower() == 'y'
setup.typescript = typescript
# Output
output_file = input("\nOutput file (leave empty for stdout): ").strip()
print("\n🚀 Generating scene...\n")
try:
code = setup.generate()
if output_file:
with open(output_file, 'w') as f:
f.write(code)
print(f"✅ Scene generated → {output_file}")
else:
print(code)
except Exception as e:
print(f"❌ Error: {e}", file=sys.stderr)
sys.exit(1)
def main():
parser = argparse.ArgumentParser(
description='Generate React Three Fiber scene setup',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog=__doc__
)
parser.add_argument(
'--preset',
default='standard',
choices=['minimal', 'standard', 'performance', 'creative', 'product', 'game'],
help='Scene preset (default: standard)'
)
parser.add_argument(
'--output',
help='Output file path (default: stdout)'
)
parser.add_argument(
'--typescript',
action='store_true',
help='Generate TypeScript code'
)
parser.add_argument(
'--lighting',
help='Comma-separated lighting types (ambient,directional,point,hemisphere,spot)'
)
parser.add_argument(
'--controls',
choices=['orbit', 'fly', 'pointer-lock'],
help='Camera controls type'
)
parser.add_argument(
'--environment',
choices=['drei-preset', 'sky', 'hdri', 'none'],
help='Environment type'
)
parser.add_argument(
'--shadows',
action='store_true',
help='Enable shadows'
)
parser.add_argument(
'--post-processing',
action='store_true',
help='Enable post-processing effects'
)
parser.add_argument(
'--performance',
action='store_true',
help='Enable performance optimizations'
)
parser.add_argument(
'--physics',
action='store_true',
help='Enable physics'
)
parser.add_argument(
'--interactive',
action='store_true',
help='Run in interactive mode'
)
args = parser.parse_args()
# Run interactive mode if requested
if args.interactive or len(sys.argv) == 1:
interactive_mode()
return
# Parse lighting
lighting = None
if args.lighting:
lighting = [l.strip() for l in args.lighting.split(',')]
# Parse environment
environment = args.environment if args.environment != 'none' else None
# Generate scene
setup = R3FSceneSetup(
preset=args.preset,
typescript=args.typescript,
lighting=lighting,
controls=args.controls,
environment=environment,
shadows=args.shadows,
post_processing=args.post_processing,
performance=args.performance,
physics=args.physics,
)
try:
code = setup.generate()
# Output
if args.output:
with open(args.output, 'w') as f:
f.write(code)
print(f"✅ Scene generated → {args.output}")
else:
print(code)
except Exception as e:
print(f"❌ Error: {e}", file=sys.stderr)
sys.exit(1)
if __name__ == '__main__':
main()
Related skills
How it compares
Use react-three-fiber for React-integrated 3D UI patterns; use raw Three.js skills when React is not in the stack.
FAQ
What does react-three-fiber do?
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 ma
When should I use react-three-fiber?
During build frontend work for frontend development.
Is react-three-fiber safe to install?
Review the Security Audits panel on this listing before production use.