r3f-fundamentals

安装量: 283
排名: #3177

安装

npx skills add https://github.com/enzed/r3f-skills --skill r3f-fundamentals

React Three Fiber Fundamentals Quick Start import { Canvas } from '@react-three/fiber' import { useRef } from 'react' import { useFrame } from '@react-three/fiber'

function RotatingBox() { const meshRef = useRef()

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

return ( ) }

export default function App() { return ( ) }

Canvas Component

The root component that creates the WebGL context, scene, camera, and renderer.

import { Canvas } from '@react-three/fiber'

function App() { return ( <Canvas // Camera configuration camera={{ position: [0, 5, 10], fov: 75, near: 0.1, far: 1000, }} // Or use orthographic orthographic camera={{ zoom: 50, position: [0, 0, 100] }}

  // Renderer settings
  gl={{
    antialias: true,
    alpha: true,
    powerPreference: 'high-performance',
    preserveDrawingBuffer: true,  // For screenshots
  }}
  dpr={[1, 2]}  // Pixel ratio min/max

  // Shadows
  shadows  // or shadows="soft" | "basic" | "percentage"

  // Color management
  flat  // Disable automatic sRGB color management

  // Frame loop control
  frameloop="demand"  // 'always' | 'demand' | 'never'

  // Event handling
  eventSource={document.getElementById('root')}
  eventPrefix="client"  // 'offset' | 'client' | 'page' | 'layer' | 'screen'

  // Callbacks
  onCreated={(state) => {
    console.log('Canvas ready:', state.gl, state.scene, state.camera)
  }}
  onPointerMissed={() => console.log('Clicked background')}

  // Styling
  style={{ width: '100%', height: '100vh' }}
>
  <Scene />
</Canvas>

) }

Canvas Defaults

R3F sets sensible defaults:

Renderer: antialias, alpha, outputColorSpace = SRGBColorSpace Camera: PerspectiveCamera at [0, 0, 5] Scene: Automatic resize handling Events: Pointer events enabled useFrame Hook

Subscribe to the render loop. Called every frame (typically 60fps).

import { useFrame } from '@react-three/fiber' import { useRef } from 'react'

function AnimatedMesh() { const meshRef = useRef()

useFrame((state, delta, xrFrame) => { // state: Full R3F state (see useThree) // delta: Time since last frame in seconds // xrFrame: XR frame if in VR/AR mode

// Animate rotation
meshRef.current.rotation.y += delta

// Access clock
const elapsed = state.clock.elapsedTime
meshRef.current.position.y = Math.sin(elapsed) * 2

// Access pointer position (-1 to 1)
const { x, y } = state.pointer
meshRef.current.rotation.x = y * 0.5
meshRef.current.rotation.z = x * 0.5

})

return ( ) }

useFrame with Priority

Control render order with priority (higher = later).

// Default priority is 0 useFrame((state, delta) => { // Runs first }, -1)

useFrame((state, delta) => { // Runs after priority -1 }, 0)

// Manual rendering with positive priority useFrame((state, delta) => { // Take over rendering state.gl.render(state.scene, state.camera) }, 1)

Conditional useFrame function ConditionalAnimation({ active }) { useFrame((state, delta) => { if (!active) return // Skip when inactive meshRef.current.rotation.y += delta }) }

useThree Hook

Access the R3F state store.

import { useThree } from '@react-three/fiber'

function CameraInfo() { // Get full state (triggers re-render on any change) const state = useThree()

// Selective subscription (recommended) const camera = useThree((state) => state.camera) const gl = useThree((state) => state.gl) const scene = useThree((state) => state.scene) const size = useThree((state) => state.size)

// Available state properties: // gl: WebGLRenderer // scene: Scene // camera: Camera // raycaster: Raycaster // pointer: Vector2 (normalized -1 to 1) // mouse: Vector2 (deprecated, use pointer) // clock: Clock // size: { width, height, top, left } // viewport: { width, height, factor, distance, aspect } // performance: { current, min, max, debounce, regress } // events: Event handlers // set: State setter // get: State getter // invalidate: Trigger re-render (for frameloop="demand") // advance: Advance one frame (for frameloop="never")

return null }

Common useThree Patterns // Responsive to viewport function ResponsiveObject() { const viewport = useThree((state) => state.viewport) return ( ) }

// Manual render trigger function TriggerRender() { const invalidate = useThree((state) => state.invalidate)

const handleClick = () => { // Trigger render when using frameloop="demand" invalidate() } }

// Update camera function CameraController() { const camera = useThree((state) => state.camera) const set = useThree((state) => state.set)

useEffect(() => { camera.position.set(10, 10, 10) camera.lookAt(0, 0, 0) }, [camera]) }

JSX Elements

All Three.js objects are available as JSX elements (camelCase).

Meshes // Basic mesh structure <mesh position={[0, 0, 0]} // x, y, z rotation={[0, Math.PI, 0]} // Euler angles in radians scale={[1, 2, 1]} // x, y, z or single number visible={true} castShadow receiveShadow

// With ref const meshRef = useRef() // meshRef.current is the THREE.Mesh

Geometry args

Constructor arguments via args prop:

// BoxGeometry(width, height, depth, widthSegments, heightSegments, depthSegments)

// SphereGeometry(radius, widthSegments, heightSegments)

// PlaneGeometry(width, height, widthSegments, heightSegments)

// CylinderGeometry(radiusTop, radiusBottom, height, radialSegments)

Groups

Nested Properties

Use dashes for nested properties:

<mesh position-x={5} rotation-y={Math.PI} scale-z={2}

// Shadow camera properties

attach Prop

Control how children attach to parents:

{/ Default: attaches as 'material' /}

{/ Explicit attach /}

{/ Array attachment /}

{/ Custom attachment with function /} { parent.map = self return () => { parent.map = null } // Cleanup }} />

Event Handling

R3F provides React-style events on 3D objects.

function InteractiveBox() { const [hovered, setHovered] = useState(false) const [clicked, setClicked] = useState(false)

return ( { e.stopPropagation() // Prevent bubbling setClicked(!clicked)

    // Event properties:
    console.log(e.object)      // THREE.Mesh
    console.log(e.point)       // Vector3 - intersection point
    console.log(e.distance)    // Distance from camera
    console.log(e.face)        // Intersected face
    console.log(e.faceIndex)   // Face index
    console.log(e.uv)          // UV coordinates
    console.log(e.normal)      // Face normal
    console.log(e.pointer)     // Normalized pointer coords
    console.log(e.ray)         // Raycaster ray
    console.log(e.camera)      // Camera
    console.log(e.delta)       // Distance moved (drag events)
  }}
  onContextMenu={(e) => console.log('Right click')}
  onDoubleClick={(e) => console.log('Double click')}
  onPointerOver={(e) => {
    e.stopPropagation()
    setHovered(true)
    document.body.style.cursor = 'pointer'
  }}
  onPointerOut={(e) => {
    setHovered(false)
    document.body.style.cursor = 'default'
  }}
  onPointerDown={(e) => console.log('Pointer down')}
  onPointerUp={(e) => console.log('Pointer up')}
  onPointerMove={(e) => console.log('Moving over mesh')}
  onWheel={(e) => console.log('Wheel:', e.deltaY)}
  scale={hovered ? 1.2 : 1}
>
  <boxGeometry />
  <meshStandardMaterial color={clicked ? 'hotpink' : 'orange'} />
</mesh>

) }

Event Propagation

Events bubble up through the scene graph:

console.log('Group clicked')}> { e.stopPropagation() // Stop bubbling to group console.log('Mesh clicked') }}>

primitive Element

Use existing Three.js objects directly:

import * as THREE from 'three'

// Existing object const geometry = new THREE.BoxGeometry() const material = new THREE.MeshStandardMaterial({ color: 'red' }) const mesh = new THREE.Mesh(geometry, material)

function Scene() { return }

// Common with loaded models function Model({ gltf }) { return }

extend Function

Register custom Three.js classes for JSX use:

import { extend } from '@react-three/fiber' import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'

// Extend once (usually at module level) extend({ OrbitControls })

// Now use as JSX function Scene() { const { camera, gl } = useThree() return }

// TypeScript declaration declare global { namespace JSX { interface IntrinsicElements { orbitControls: ReactThreeFiber.Object3DNode } } }

Refs and Imperative Access import { useRef, useEffect } from 'react' import { useFrame } from '@react-three/fiber' import * as THREE from 'three'

function MeshWithRef() { const meshRef = useRef(null) const materialRef = useRef(null)

useEffect(() => { if (meshRef.current) { // Direct Three.js access meshRef.current.geometry.computeBoundingBox() console.log(meshRef.current.geometry.boundingBox) } }, [])

useFrame(() => { if (materialRef.current) { materialRef.current.color.setHSL(Math.random(), 1, 0.5) } })

return ( ) }

Performance Patterns Avoiding Re-renders // BAD: Creates new object every render

// GOOD: Mutate existing position const meshRef = useRef() useFrame(() => { meshRef.current.position.x = x })

// GOOD: Use useMemo for static values const position = useMemo(() => [x, y, z], [x, y, z])

Component Isolation // Isolate animated components to prevent parent re-renders function Scene() { return ( <> {/ Only this re-renders on animation /} </> ) }

function AnimatedObject() { const ref = useRef() useFrame((_, delta) => { ref.current.rotation.y += delta }) return }

Dispose

R3F auto-disposes geometries, materials, and textures. Override with:

{/ Prevent auto-dispose /}

Common Patterns Fullscreen Canvas // styles.css html, body, #root { margin: 0; padding: 0; width: 100%; height: 100%; }

// App.tsx

Responsive Canvas function ResponsiveScene() { const { viewport } = useThree() return ( ) } Forwarding Refs import { forwardRef } from 'react' const CustomMesh = forwardRef((props, ref) => { return ( ) }) // Usage const meshRef = useRef() Debugging with Leva Leva provides a GUI for tweaking parameters in real-time during development. Installation npm install leva Basic Controls import { useControls } from 'leva' function DebugMesh() { const { position, color, scale, visible } = useControls({ position: { value: [0, 0, 0], step: 0.1 }, color: '#ff0000', scale: { value: 1, min: 0.1, max: 5, step: 0.1 }, visible: true, }) return ( ) } Organized Folders import { useControls, folder } from 'leva' function DebugScene() { const { lightIntensity, lightColor, shadowMapSize } = useControls({ Lighting: folder({ lightIntensity: { value: 1, min: 0, max: 5 }, lightColor: '#ffffff', shadowMapSize: { value: 1024, options: [512, 1024, 2048, 4096] }, }), Camera: folder({ fov: { value: 75, min: 30, max: 120 }, near: { value: 0.1, min: 0.01, max: 1 }, }), }) return ( ) } Button Actions import { useControls, button } from 'leva' function DebugActions() { const meshRef = useRef() useControls({ 'Reset Position': button(() => { meshRef.current.position.set(0, 0, 0) }), 'Random Color': button(() => { meshRef.current.material.color.setHex(Math.random() * 0xffffff) }), 'Log State': button(() => { console.log(meshRef.current.position) }), }) return ... } Hide in Production import { Leva } from 'leva' function App() { return ( <> {/* Hide Leva panel in production */}
返回排行榜