Install
Terminal · npx$
npx skills add https://github.com/enzed/r3f-skills --skill r3f-interactionWorks with Paperclip
How R3f Interaction fits into a Paperclip company.
R3f Interaction drops into any Paperclip agent that handles this kind of work. Assign it to a specialist inside a pre-configured PaperclipOrg company and the skill becomes available on every heartbeat — no prompt engineering, no tool wiring.
S
SaaS FactoryPaired
Pre-configured AI company — 18 agents, 18 skills, one-time purchase.
$27$59
Explore packSource file
SKILL.md885 linesExpandCollapse
---name: r3f-interactiondescription: React Three Fiber interaction - pointer events, controls, gestures, selection. Use when handling user input, implementing click detection, adding camera controls, or creating interactive 3D experiences.--- # React Three Fiber Interaction ## Quick Start ```tsximport { Canvas } from '@react-three/fiber'import { OrbitControls } from '@react-three/drei' function InteractiveMesh() { return ( <mesh onClick={(e) => console.log('Clicked!', e.point)} onPointerOver={(e) => console.log('Hover')} onPointerOut={(e) => console.log('Unhover')} > <boxGeometry /> <meshStandardMaterial color="hotpink" /> </mesh> )} export default function App() { return ( <Canvas> <ambientLight /> <InteractiveMesh /> <OrbitControls /> </Canvas> )}``` ## Pointer Events R3F provides built-in pointer events on mesh elements. ### Available Events ```tsx<mesh // Click events onClick={(e) => {}} // Click (pointerdown + pointerup on same object) onDoubleClick={(e) => {}} // Double click onContextMenu={(e) => {}} // Right click // Pointer events onPointerDown={(e) => {}} // Pointer pressed onPointerUp={(e) => {}} // Pointer released onPointerMove={(e) => {}} // Pointer moved while over object onPointerOver={(e) => {}} // Pointer enters object onPointerOut={(e) => {}} // Pointer leaves object onPointerEnter={(e) => {}} // Pointer enters object (no bubbling) onPointerLeave={(e) => {}} // Pointer leaves object (no bubbling) onPointerMissed={(e) => {}} // Click that missed all objects // Wheel onWheel={(e) => {}} // Mouse wheel // Touch onPointerCancel={(e) => {}} // Touch cancelled> <boxGeometry /> <meshStandardMaterial /></mesh>``` ### Event Object ```tsxfunction InteractiveMesh() { const handleClick = (event) => { // Stop propagation to parent objects event.stopPropagation() // Event properties console.log({ object: event.object, // The mesh that was clicked point: event.point, // World coordinates of intersection distance: event.distance, // Distance from camera face: event.face, // Intersected face faceIndex: event.faceIndex, // Face index uv: event.uv, // UV coordinates at intersection normal: event.normal, // Face normal camera: event.camera, // Current camera ray: event.ray, // Ray used for intersection intersections: event.intersections, // All intersections nativeEvent: event.nativeEvent, // Original DOM event delta: event.delta, // Click distance (useful for drag detection) }) } return ( <mesh onClick={handleClick}> <boxGeometry /> <meshStandardMaterial /> </mesh> )}``` ### Hover Effects ```tsximport { useState } from 'react' function HoverableMesh() { 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 = 'default' }} scale={hovered ? 1.2 : 1} > <boxGeometry /> <meshStandardMaterial color={hovered ? 'hotpink' : 'orange'} /> </mesh> )}``` ### Selective Raycasting ```tsx// Disable raycasting for specific objects<mesh raycast={() => null}> <boxGeometry /> <meshStandardMaterial /></mesh> // Or use layers<mesh layers={1} // Only raycast against layer 1 onClick={() => console.log('clicked')}> <boxGeometry /> <meshStandardMaterial /></mesh>``` ## Camera Controls ### OrbitControls ```tsximport { OrbitControls } from '@react-three/drei' function Scene() { return ( <> <mesh> <boxGeometry /> <meshStandardMaterial /> </mesh> <OrbitControls makeDefault // Use as default controls enableDamping // Smooth movement dampingFactor={0.05} enableZoom={true} enablePan={true} enableRotate={true} autoRotate={false} autoRotateSpeed={2} minDistance={2} maxDistance={50} minPolarAngle={0} // Top limit maxPolarAngle={Math.PI / 2} // Horizon limit minAzimuthAngle={-Math.PI / 4} // Left limit maxAzimuthAngle={Math.PI / 4} // Right limit target={[0, 1, 0]} // Look-at point /> </> )}``` ### OrbitControls with Ref ```tsximport { OrbitControls } from '@react-three/drei'import { useRef, useEffect } from 'react' function Scene() { const controlsRef = useRef() useEffect(() => { // Access controls methods if (controlsRef.current) { controlsRef.current.reset() controlsRef.current.target.set(0, 1, 0) controlsRef.current.update() } }, []) return <OrbitControls ref={controlsRef} />}``` ### MapControls Top-down map-style controls. ```tsximport { MapControls } from '@react-three/drei' <MapControls enableDamping dampingFactor={0.05} screenSpacePanning={false} // Pan in world space maxPolarAngle={Math.PI / 2}/>``` ### FlyControls Free-flying camera controls. ```tsximport { FlyControls } from '@react-three/drei' <FlyControls movementSpeed={10} rollSpeed={Math.PI / 24} dragToLook/>``` ### FirstPersonControls FPS-style controls. ```tsximport { FirstPersonControls } from '@react-three/drei' <FirstPersonControls movementSpeed={10} lookSpeed={0.1} lookVertical/>``` ### PointerLockControls Lock pointer for FPS games. ```tsximport { PointerLockControls } from '@react-three/drei'import { useRef } from 'react' function Scene() { const controlsRef = useRef() return ( <> <PointerLockControls ref={controlsRef} /> {/* Click to lock pointer */} <mesh onClick={() => controlsRef.current?.lock()}> <planeGeometry args={[10, 10]} /> <meshBasicMaterial color="green" /> </mesh> </> )}``` ### CameraControls Advanced camera controls with smooth transitions. ```tsximport { CameraControls } from '@react-three/drei'import { useRef } from 'react' function Scene() { const controlsRef = useRef() const focusOnObject = async () => { // Smooth transition to target await controlsRef.current?.setLookAt( 5, 3, 5, // Camera position 0, 0, 0, // Look-at target true // Enable transition ) } return ( <> <CameraControls ref={controlsRef} /> <mesh onClick={focusOnObject}> <boxGeometry /> <meshStandardMaterial color="red" /> </mesh> </> )}``` ### TrackballControls Unconstrained rotation controls. ```tsximport { TrackballControls } from '@react-three/drei' <TrackballControls rotateSpeed={2.0} zoomSpeed={1.2} panSpeed={0.8} staticMoving={true}/>``` ### ArcballControls Arc-based rotation controls. ```tsximport { ArcballControls } from '@react-three/drei' <ArcballControls enableAnimations dampingFactor={25}/>``` ## Transform Controls Gizmo for moving/rotating/scaling objects. ```tsximport { TransformControls, OrbitControls } from '@react-three/drei'import { useRef, useState } from 'react' function Scene() { const meshRef = useRef() const [mode, setMode] = useState('translate') const orbitRef = useRef() return ( <> <OrbitControls ref={orbitRef} makeDefault /> <TransformControls object={meshRef} mode={mode} // 'translate' | 'rotate' | 'scale' space="local" // 'local' | 'world' onMouseDown={() => { // Disable orbit while transforming if (orbitRef.current) orbitRef.current.enabled = false }} onMouseUp={() => { if (orbitRef.current) orbitRef.current.enabled = true }} /> <mesh ref={meshRef}> <boxGeometry /> <meshStandardMaterial color="orange" /> </mesh> {/* Mode switching buttons in HTML */} <div className="controls"> <button onClick={() => setMode('translate')}>Move</button> <button onClick={() => setMode('rotate')}>Rotate</button> <button onClick={() => setMode('scale')}>Scale</button> </div> </> )}``` ### PivotControls Alternative transform gizmo with pivot point. ```tsximport { PivotControls } from '@react-three/drei' function Scene() { return ( <PivotControls anchor={[0, 0, 0]} // Anchor point depthTest={false} // Always visible lineWidth={2} // Axis line width axisColors={['red', 'green', 'blue']} scale={1} // Gizmo scale fixed={false} // Fixed screen size > <mesh> <boxGeometry /> <meshStandardMaterial color="orange" /> </mesh> </PivotControls> )}``` ## Drag Controls ### useDrag from @use-gesture/react ```bashnpm install @use-gesture/react``` ```tsximport { useDrag } from '@use-gesture/react'import { useSpring, animated } from '@react-spring/three'import { useThree } from '@react-three/fiber' function DraggableMesh() { const { size, viewport } = useThree() const aspect = size.width / viewport.width const [spring, api] = useSpring(() => ({ position: [0, 0, 0], config: { mass: 1, tension: 280, friction: 60 } })) const bind = useDrag(({ movement: [mx, my], down }) => { api.start({ position: down ? [mx / aspect, -my / aspect, 0] : [0, 0, 0] }) }) return ( <animated.mesh {...bind()} position={spring.position}> <boxGeometry /> <meshStandardMaterial color="hotpink" /> </animated.mesh> )}``` ### DragControls (Drei) ```tsximport { DragControls, OrbitControls } from '@react-three/drei'import { useRef } from 'react' function Scene() { const meshRef = useRef() const orbitRef = useRef() return ( <> <OrbitControls ref={orbitRef} makeDefault /> <DragControls onDragStart={() => { if (orbitRef.current) orbitRef.current.enabled = false }} onDragEnd={() => { if (orbitRef.current) orbitRef.current.enabled = true }} > <mesh ref={meshRef}> <boxGeometry /> <meshStandardMaterial color="orange" /> </mesh> </DragControls> </> )}``` ## Keyboard Controls ### KeyboardControls (Drei) ```tsximport { KeyboardControls, useKeyboardControls } from '@react-three/drei'import { useFrame } from '@react-three/fiber'import { useRef } from 'react' // Define key mappingsconst keyMap = [ { name: 'forward', keys: ['ArrowUp', 'KeyW'] }, { name: 'backward', keys: ['ArrowDown', 'KeyS'] }, { name: 'left', keys: ['ArrowLeft', 'KeyA'] }, { name: 'right', keys: ['ArrowRight', 'KeyD'] }, { name: 'jump', keys: ['Space'] }, { name: 'sprint', keys: ['ShiftLeft'] },] function Player() { const meshRef = useRef() const [, getKeys] = useKeyboardControls() useFrame((state, delta) => { const { forward, backward, left, right, jump, sprint } = getKeys() const speed = sprint ? 10 : 5 if (forward) meshRef.current.position.z -= speed * delta if (backward) meshRef.current.position.z += speed * delta if (left) meshRef.current.position.x -= speed * delta if (right) meshRef.current.position.x += speed * delta if (jump) meshRef.current.position.y += speed * delta }) return ( <mesh ref={meshRef}> <boxGeometry /> <meshStandardMaterial color="blue" /> </mesh> )} export default function App() { return ( <KeyboardControls map={keyMap}> <Canvas> <ambientLight /> <Player /> </Canvas> </KeyboardControls> )}``` ### Subscribe to Key Changes ```tsximport { useKeyboardControls } from '@react-three/drei'import { useEffect } from 'react' function KeyListener() { const jumpPressed = useKeyboardControls((state) => state.jump) useEffect(() => { if (jumpPressed) { console.log('Jump!') } }, [jumpPressed]) return null}``` ## Selection System ### Click to Select ```tsximport { useState } from 'react' function SelectableScene() { const [selected, setSelected] = useState(null) return ( <> {[[-2, 0, 0], [0, 0, 0], [2, 0, 0]].map((position, i) => ( <mesh key={i} position={position} onClick={(e) => { e.stopPropagation() setSelected(i) }} > <boxGeometry /> <meshStandardMaterial color={selected === i ? 'hotpink' : 'orange'} emissive={selected === i ? 'hotpink' : 'black'} emissiveIntensity={0.3} /> </mesh> ))} {/* Click on empty space to deselect */} <mesh position={[0, -1, 0]} rotation={[-Math.PI / 2, 0, 0]} onClick={() => setSelected(null)} > <planeGeometry args={[20, 20]} /> <meshStandardMaterial color="gray" /> </mesh> </> )}``` ### Multi-Select with Outline ```tsximport { useState } from 'react'import { EffectComposer, Outline, Selection, Select } from '@react-three/postprocessing' function MultiSelectScene() { const [selected, setSelected] = useState(new Set()) const toggleSelect = (id, event) => { event.stopPropagation() setSelected((prev) => { const next = new Set(prev) if (event.shiftKey) { // Multi-select with shift if (next.has(id)) { next.delete(id) } else { next.add(id) } } else { // Single select next.clear() next.add(id) } return next }) } return ( <Selection> <EffectComposer autoClear={false}> <Outline blur visibleEdgeColor={0xffffff} edgeStrength={10} /> </EffectComposer> {[0, 1, 2, 3, 4].map((id) => ( <Select key={id} enabled={selected.has(id)}> <mesh position={[(id - 2) * 2, 0, 0]} onClick={(e) => toggleSelect(id, e)} > <boxGeometry /> <meshStandardMaterial color="orange" /> </mesh> </Select> ))} </Selection> )}``` ## Screen-Space to World-Space ### Get World Position from Click ```tsximport { useThree } from '@react-three/fiber'import * as THREE from 'three' function ClickToPlace() { const { camera, raycaster, pointer } = useThree() const planeRef = useRef() const handleClick = (event) => { // Create intersection plane const plane = new THREE.Plane(new THREE.Vector3(0, 1, 0), 0) const intersection = new THREE.Vector3() // Cast ray from pointer raycaster.setFromCamera(pointer, camera) raycaster.ray.intersectPlane(plane, intersection) console.log('World position:', intersection) } return ( <mesh ref={planeRef} rotation={[-Math.PI / 2, 0, 0]} onClick={handleClick} > <planeGeometry args={[100, 100]} /> <meshBasicMaterial visible={false} /> </mesh> )}``` ### World Position to Screen Position ```tsximport { useThree, useFrame } from '@react-three/fiber'import { Html } from '@react-three/drei'import * as THREE from 'three' function WorldToScreen({ target }) { const { camera, size } = useThree() const getScreenPosition = (worldPos) => { const vector = worldPos.clone() vector.project(camera) return { x: (vector.x * 0.5 + 0.5) * size.width, y: (1 - (vector.y * 0.5 + 0.5)) * size.height } } // Or use Html component which handles this automatically return ( <Html position={target}> <div className="label">Label</div> </Html> )}``` ## Gesture Recognition ### usePinch and useWheel ```tsximport { usePinch, useWheel } from '@use-gesture/react'import { useSpring, animated } from '@react-spring/three' function ZoomableMesh() { const [spring, api] = useSpring(() => ({ scale: 1, config: { mass: 1, tension: 200, friction: 30 } })) usePinch( ({ offset: [s] }) => { api.start({ scale: s }) }, { target: window } ) useWheel( ({ delta: [, dy] }) => { api.start({ scale: spring.scale.get() - dy * 0.001 }) }, { target: window } ) return ( <animated.mesh scale={spring.scale}> <boxGeometry /> <meshStandardMaterial color="cyan" /> </animated.mesh> )}``` ## Scroll Controls ```tsximport { Canvas } from '@react-three/fiber'import { ScrollControls, Scroll, useScroll } from '@react-three/drei'import { useFrame } from '@react-three/fiber'import { useRef } from 'react' function AnimatedOnScroll() { const meshRef = useRef() const scroll = useScroll() useFrame(() => { const offset = scroll.offset // 0 to 1 meshRef.current.rotation.y = offset * Math.PI * 2 meshRef.current.position.y = offset * 5 }) return ( <mesh ref={meshRef}> <boxGeometry /> <meshStandardMaterial color="orange" /> </mesh> )} export default function App() { return ( <Canvas> <ScrollControls pages={3} damping={0.25}> <Scroll> <AnimatedOnScroll /> </Scroll> {/* HTML content that scrolls */} <Scroll html> <h1 style={{ position: 'absolute', top: '10vh' }}>Page 1</h1> <h1 style={{ position: 'absolute', top: '110vh' }}>Page 2</h1> <h1 style={{ position: 'absolute', top: '210vh' }}>Page 3</h1> </Scroll> </ScrollControls> </Canvas> )}``` ## Presentation Controls For product showcases with limited rotation. ```tsximport { PresentationControls } from '@react-three/drei' function ProductShowcase() { return ( <PresentationControls global // Apply to whole scene snap // Snap back when released speed={1} // Rotation speed zoom={1} // Zoom speed rotation={[0, 0, 0]} // Initial rotation polar={[-Math.PI / 4, Math.PI / 4]} // Vertical limits azimuth={[-Math.PI / 4, Math.PI / 4]} // Horizontal limits config={{ mass: 1, tension: 170, friction: 26 }} > <mesh> <boxGeometry /> <meshStandardMaterial color="gold" /> </mesh> </PresentationControls> )}``` ## Performance Tips 1. **Stop propagation**: Prevent unnecessary raycasts2. **Use layers**: Filter raycast targets3. **Simpler collision meshes**: Use invisible simple geometry4. **Throttle events**: Limit onPointerMove frequency5. **Disable controls when not needed**: `enabled={false}` ```tsx// Use simpler geometry for raycastingfunction OptimizedInteraction() { return ( <group> {/* Complex visible mesh */} <mesh raycast={() => null}> <torusKnotGeometry args={[1, 0.4, 100, 16]} /> <meshStandardMaterial color="purple" /> </mesh> {/* Simple invisible collision mesh */} <mesh onClick={() => console.log('clicked')}> <sphereGeometry args={[1.5]} /> <meshBasicMaterial visible={false} /> </mesh> </group> )} // Throttle pointer move eventsimport { useMemo, useCallback } from 'react'import throttle from 'lodash/throttle' function ThrottledHover() { const handleMove = useMemo( () => throttle((e) => { console.log('Move', e.point) }, 100), [] ) return ( <mesh onPointerMove={handleMove}> <boxGeometry /> <meshStandardMaterial /> </mesh> )}``` ## See Also - `r3f-fundamentals` - Canvas and scene setup- `r3f-animation` - Animating interactions- `r3f-postprocessing` - Visual feedback effects (outline, selection)Related skills
1password
Install 1password skill for Claude Code from steipete/clawdis.
3d Web Experience
Install 3d Web Experience skill for Claude Code from sickn33/antigravity-awesome-skills.
Ab Test Setup
This handles the full A/B testing workflow from hypothesis formation to statistical analysis. It walks you through proper test design, calculates sample sizes,