Loading...
Loading...
Building three.js scenes from React with React Three Fiber and drei: renderer setup, the scene graph as JSX, frame-loop, state and loading hooks, pointer events, and adaptive performance, on WebGL or with WebGPU and TSL. Use when writing React components that render three.js.
npx skill4agent add cloudai-x/threejs-skills threejs-react-three-fiber@react-three/fiber@react-three/dreiimport { useRef } from "react";
import { Canvas, useFrame } from "@react-three/fiber";
import { OrbitControls } from "@react-three/drei";
function Box() {
const ref = useRef();
useFrame((_, delta) => {
ref.current.rotation.y += delta;
});
return (
<mesh ref={ref} position={[0, 0.5, 0]} castShadow>
<boxGeometry args={[1, 1, 1]} />
<meshStandardMaterial color="#d9915b" />
</mesh>
);
}
export default function App() {
return (
<Canvas shadows="percentage" camera={{ position: [3, 2, 4], fov: 50 }}>
<color attach="background" args={["#111827"]} />
<ambientLight intensity={0.4} />
<directionalLight position={[3, 4, 2]} intensity={2} castShadow />
<Box />
<mesh rotation-x={-Math.PI / 2} receiveShadow>
<planeGeometry args={[10, 10]} />
<meshStandardMaterial color="#20293a" />
</mesh>
<OrbitControls makeDefault />
</Canvas>
);
}export function App() {
return (
<Canvas
shadows="variance"
dpr={[1, 2]}
frameloop="demand"
flat
camera={{ position: [0, 2, 6], fov: 45 }}
gl={{ powerPreference: "high-performance" }}
onCreated={(state) => state.gl.setClearColor("#111827")}
>
{/* scene contents */}
</Canvas>
);
}cameraPerspectiveCameraorthographiccamera={{ manual: true }}dpr[min, max]window.devicePixelRatioshadowstrue'basic''percentage''variance'shadowMaptrue'soft'false'percentage'flatlinearlegacyglnew WebGLRenderer({ canvas, antialias: true, alpha: true, powerPreference: "high-performance" })frameloop'always''demand'invalidate()'never'advance(timestamp)useFrameeventseventSourceeventPrefix'offset''page''client''layer''screen'onCreatedonPointerMissed<mesh>THREE.Mesh<meshStandardMaterial>.set()position={[1, 2, 3]}position={new THREE.Vector3(1, 2, 3)}position={5}color="#d9915b"rotation-xmaterial-colorgeometry-attributes-positionattach="material-0"argsattachfunction Torus() {
return (
<mesh position={[0, 1, 0]}>
<torusKnotGeometry args={[0.7, 0.25, 128, 24]} />
<meshStandardMaterial
color="#42699c"
roughness={0.2}
metalness={0.8}
onUpdate={(self) => console.log(self.type)}
/>
</mesh>
);
}<primitive object={obj} />dispose={null}extendimport { extend } from "@react-three/fiber";
import { Water } from "three/addons/objects/Water.js";
extend({ Water });
// then render <water args={[geometry]} /> anywhere in the CanvaslineaudiosourcepaththreeLinethreeAudiothreeSourcethreePathuseFrame(callback, renderPriority?)import { useRef } from "react";
import { useFrame } from "@react-three/fiber";
function Spinner({ radius = 2 }) {
const ref = useRef();
useFrame((state, delta) => {
ref.current.rotation.y += delta;
ref.current.position.x = Math.sin(state.clock.elapsedTime) * radius;
});
return (
<mesh ref={ref}>
<sphereGeometry args={[0.4, 32, 16]} />
<meshBasicMaterial color="#e8b4a0" />
</mesh>
);
}renderPrioritystate.gl.render(state.scene, state.camera)gl.renderuseThree(selector?)glcamerasceneraycasterpointersizeviewportinvalidatesetDprsetFrameloopcontrolsperformanceimport { useThree } from "@react-three/fiber";
const camera = useThree((state) => state.camera);
const invalidate = useThree((state) => state.invalidate);useFramestatestate.clockTHREE.ClockuseLoader(ClassOrInstance, url, extensions?, onProgress?)useLoaderuseGraph(object){ nodes, materials, meshes }import { useGraph, useLoader } from "@react-three/fiber";
import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
function Model({ url }) {
const gltf = useLoader(GLTFLoader, url, (loader) => loader.setPath("assets/"));
const { nodes } = useGraph(gltf.scene);
return <primitive object={gltf.scene} />;
}useLoader.preload(Class, url)useLoader.clear(Class, url)objecteventObjectpointdistancefaceinstanceIdintersectionsraycameraunprojectedPointdeltanativeEventonClickonDoubleClickonContextMenuonWheelonPointerUponPointerDownonPointerMoveonPointerOveronPointerOutonPointerEnteronPointerLeaveonPointerMissedonPointerCancelonLostPointerCaptureimport { useState } from "react";
function Pickable() {
const [hovered, setHovered] = useState(false);
return (
<mesh
onClick={(event) => {
event.stopPropagation(); // underlying objects along the ray no longer receive it
console.log(event.object.name, event.distance, event.point, event.instanceId);
}}
onPointerOver={(event) => {
event.stopPropagation();
setHovered(true);
}}
onPointerOut={() => setHovered(false)}
onPointerDown={(event) => {
event.target.setPointerCapture(event.pointerId); // this pointer's events reach the mesh off its surface until release
}}
onPointerUp={(event) => {
event.target.releasePointerCapture(event.pointerId);
}}
onPointerMissed={() => console.log("clicked elsewhere")}
>
<sphereGeometry args={[0.75, 32, 16]} />
<meshStandardMaterial color={hovered ? "#e8b4a0" : "#42699c"} />
</mesh>
);
}intersectObject(object, true)raycaster={{ params: { Line: { threshold: 0.1 } } }}import { Suspense } from "react";
import { Canvas } from "@react-three/fiber";
import { useGLTF, useProgress, Environment } from "@react-three/drei";
function Model({ url }) {
const { scene, animations, nodes, materials } = useGLTF(url);
return <primitive object={scene} />;
}
function Hud() {
const { active, progress, errors } = useProgress(); // tracks three's DefaultLoadingManager
return <div>{active ? `${progress.toFixed(0)}%` : errors.length > 0 ? "failed" : "done"}</div>;
}
export function App() {
return (
<>
<Hud />
<Canvas camera={{ position: [3, 2, 4], fov: 50 }}>
<Suspense fallback={null}>
<Environment preset="city" />
<Model url="model.glb" />
</Suspense>
</Canvas>
</>
);
}useGLTF(url)useGLTF.setDecoderPathuseGLTF(url, false)useGLTF.preload(url)sceneanimationscamerasnodesmaterialsmeshesuseMemo(() => SkeletonUtils.clone(scene), [scene])import * as SkeletonUtils from "three/addons/utils/SkeletonUtils.js"useTexture(input)const [a, b] = useTexture([x, y])mapemissiveMapenvMapcolorSpace<Preload all />alluseProgress@react-three/dreiOrbitControlsinvalidate()frameloop="demand"makeDefaultstate.controlsregressimport { OrbitControls } from "@react-three/drei";
<OrbitControls
makeDefault
enableDamping
dampingFactor={0.1}
target={[0, 1, 0]}
minDistance={2}
maxDistance={12}
maxPolarAngle={Math.PI / 2}
autoRotate
autoRotateSpeed={0.5}
/>CameraControlsfitToBoximport {
ContactShadows, Environment, Lightformer, Text, Html, Center, Bounds,
} from "@react-three/drei";
export function Dressing({ overlay }) { // overlay: a ref to a DOM element laid over the Canvas
return (
<group>
<Environment resolution={256}>
<Lightformer form="rect" intensity={2} position={[0, 4, 3]} scale={[8, 2, 1]} />
</Environment>
<ContactShadows position={[0, -1, 0]} opacity={0.6} scale={12} blur={2.4} far={4} />
<Bounds fit clip observe margin={1.5}>
<mesh>
<sphereGeometry args={[1, 32, 16]} />
<meshStandardMaterial metalness={0.9} roughness={0.2} />
</mesh>
<Center top position={[2.5, -1, 0]}>
<mesh>
<boxGeometry args={[1, 0.6, 1]} />
<meshStandardMaterial />
</mesh>
</Center>
</Bounds>
<Text position={[0.75, 1.1, 0]} fontSize={0.4} anchorX="center" anchorY="bottom" color="#e8b4a0" outlineWidth={0.02}>
three.js
</Text>
<Html portal={overlay} position={[2.5, 0.2, 0]} center distanceFactor={8} occlude="raycast">
<div>dom overlay anchored to the scene</div>
</Html>
</group>
);
}ContactShadowsframes={1}Lightformerformcircleringrectbox<Environment>TextmaxWidthtextAlignlineHeightletterSpacingcharactersHtmltransformoccludetrue"raycast"occlude="blending"Htmlportal<div ref={overlay} style={{ position: "absolute", inset: 0, pointerEvents: "none" }} />position: relativeCentertopbottomleftrightfrontbackprecise={false}onCenteredTextanchorXanchorYpositionBoundsfitclipobserveuseBounds()refreshresetmoveTolookAtfitclipimport { useEffect, useRef } from "react";
import { useThree } from "@react-three/fiber";
function OnDemand() {
const ref = useRef();
const invalidate = useThree((state) => state.invalidate);
// under <Canvas frameloop="demand"> imperative changes are invisible to React — request a frame for them
useEffect(() => {
const id = setInterval(() => {
ref.current.rotation.y += 0.5;
invalidate();
}, 100);
return () => clearInterval(id);
}, [invalidate]);
return (
<mesh ref={ref}>
<boxGeometry args={[1, 1, 1]} />
<meshNormalMaterial />
</mesh>
);
}setStateuseFrameflushSyncInstancesInstancecolorimport { Instance, Instances } from "@react-three/drei";
function Crowd({ count = 200 }) {
return (
<Instances limit={count} castShadow>
<capsuleGeometry args={[0.25, 0.8, 4, 12]} />
<meshStandardMaterial />
{Array.from({ length: count }, (_, i) => (
<Instance
key={i}
position={[(i % 15) - 7, 0.65, Math.floor(i / 15) - 7]}
color={`hsl(${(i * 37) % 360}, 60%, 55%)`}
/>
))}
</Instances>
);
}DetailedLODdistances[i]hysteresisimport { Detailed } from "@react-three/drei";
<Detailed distances={[0, 15, 40]} hysteresis={0.1}>
<mesh><sphereGeometry args={[1, 64, 32]} /><meshStandardMaterial /></mesh>
<mesh><sphereGeometry args={[1, 16, 8]} /><meshStandardMaterial /></mesh>
<mesh><sphereGeometry args={[1, 8, 4]} /><meshStandardMaterial /></mesh>
</Detailed>regressperformance.currentminAdaptiveDprPerformanceMonitordprsetDprAdaptiveDprdprimport { useState } from "react";
import { Canvas } from "@react-three/fiber";
import { OrbitControls, AdaptiveDpr, PerformanceMonitor } from "@react-three/drei";
// lower the resolution while the controls move
export function DragTunedApp() {
return (
<Canvas dpr={[1, 2]}>
<OrbitControls regress />
<AdaptiveDpr pixelated />
{/* scene contents */}
</Canvas>
);
}
// let the measured frame rate pick the resolution through the dpr prop
export function MonitoredApp() {
const [dpr, setDpr] = useState([1, 2]);
return (
<Canvas dpr={dpr}>
<OrbitControls />
<PerformanceMonitor
onDecline={() => setDpr(1)}
onIncline={() => setDpr([1, 2])}
flipflops={3}
onFallback={() => setDpr(1)}
/>
{/* scene contents */}
</Canvas>
);
}dprthreejs-performanceglgeometry={...}map={...}dispose={null}useLoaderuseGLTFuseTexturecleardispose(object)dispose()glthree/webgpuextendthree/tsluseMemouniform()material.needsUpdate = trueimport * as THREE from "three/webgpu";
import { color } from "three/tsl";
import { Canvas, extend, useFrame } from "@react-three/fiber";
import { useRef } from "react";
extend(THREE); // node materials and WebGPU classes join the element catalogue
function Knot() {
const ref = useRef();
useFrame((_, delta) => {
ref.current.rotation.y += delta;
});
return (
<mesh ref={ref}>
<torusKnotGeometry args={[0.8, 0.3, 128, 24]} />
<meshBasicNodeMaterial colorNode={color(0x42699c)} />
</mesh>
);
}
export default function App() {
return (
<Canvas
camera={{ position: [3, 2, 4], fov: 50 }}
gl={async (props) => {
const renderer = new THREE.WebGPURenderer(props);
await renderer.init();
return renderer;
}}
>
<color attach="background" args={["#111827"]} />
<Knot />
</Canvas>
);
}flat<color attach="background">flatforceWebGL: truepositionNodeuseGLTFuseTexturePreloadCenterBoundsInstancesDetailedOrbitControlsCameraControlsHtmlocclude="blending"Environmentpresetmap.hdruseProgressAdaptiveDprPerformanceMonitorTextonBeforeCompileContactShadowsonBeforeCompileShaderMaterialHtmlocclude="blending"Environmentground.jpg.webpfilesEnvironmentcolorNodepositionNodethreejs-shadersFnrequestAnimationFrame(tick)useFrame((state, delta) => { ... })state.clock.getElapsedTime()state.clock.elapsedTimeimport { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js"import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js"three/addons/examples/jsmuseFrame(() => { ref.current.rotation.y += 0.01; })useFrame((_, delta) => { ref.current.rotation.y += delta; })useFrame((_, delta) => setRotation((r) => r + delta))useFrame((_, delta) => { ref.current.rotation.y += delta; })useFrame((state, delta) => { ref.current.rotation.y += delta; }, 1)useFrame((_, delta) => { ref.current.rotation.y += delta; })state.gl.render(state.scene, state.camera)<mesh position={{ x: 1, y: 0, z: 0 }}><mesh position={[1, 0, 0]}>positionfromArraysetScalarcopyuseEffect(() => { new GLTFLoader().load(url, (gltf) => setScene(gltf.scene)); }, [])const gltf = useGLTF(url)useGLTF(url).scene.clone()useMemo(() => SkeletonUtils.clone(scene), [scene])scene.add(gltf.scene)<primitive object={gltf.scene} /><water args={[geometry]} />extend({ Water }); <water args={[geometry]} /><line><threeLine>three<Canvas shadows><Canvas shadows="percentage"><directionalLight intensity={2} /><directionalLight castShadow intensity={2} />ref.current.position.x += 1ref.current.position.x += 1; invalidate();invalidateuseThree((state) => state.invalidate)gl={new THREE.WebGPURenderer()}gl={async (props) => { const renderer = new THREE.WebGPURenderer(props); await renderer.init(); return renderer; }}new OrbitControls(camera, renderer.domElement)<OrbitControls makeDefault /><Environment><Lightformer /></Environment><Environment files="env.hdr" />.hdrthreejs-fundamentalsthreejs-loadersthreejs-interactionthreejs-shadersthreejs-performancethreejs-asset-pipeline