r3f-interaction
SKILL.md
React Three Fiber Interaction
Pointer Events
<mesh
onClick={(e) => {
e.stopPropagation()
console.log(e.point, e.distance, e.object, e.uv)
}}
onContextMenu={(e) => console.log('Right click')}
onDoubleClick={(e) => console.log('Double click')}
onPointerOver={(e) => {
e.stopPropagation()
document.body.style.cursor = 'pointer'
}}
onPointerOut={(e) => {
document.body.style.cursor = 'default'
}}
onPointerMove={(e) => console.log(e.point)}
onPointerDown={(e) => console.log('Down')}
onPointerUp={(e) => console.log('Up')}
/>
OrbitControls
import { OrbitControls } from '@react-three/drei'
<OrbitControls
enableDamping
dampingFactor={0.05}
minDistance={2}
maxDistance={50}
minPolarAngle={0}
maxPolarAngle={Math.PI / 2}
autoRotate
autoRotateSpeed={2}
/>
Other Controls
import {
MapControls,
FlyControls,
FirstPersonControls,
PointerLockControls,
TrackballControls,
} from '@react-three/drei'
<MapControls />
<FlyControls movementSpeed={10} rollSpeed={0.5} />
<PointerLockControls />
TransformControls
import { TransformControls } from '@react-three/drei'
function Movable() {
const ref = useRef()
return (
<>
<mesh ref={ref}>
<boxGeometry />
<meshStandardMaterial />
</mesh>
<TransformControls object={ref} mode="translate" />
</>
)
}
PivotControls
import { PivotControls } from '@react-three/drei'
<PivotControls anchor={[0, 0, 0]} scale={0.75}>
<mesh>
<boxGeometry />
<meshStandardMaterial />
</mesh>
</PivotControls>
Drag with Gesture
import { useDrag } from '@use-gesture/react'
import { useSpring, animated } from '@react-spring/three'
function DraggableBox() {
const [spring, api] = useSpring(() => ({ position: [0, 0, 0] }))
const bind = useDrag(({ movement: [mx, my], down }) => {
api.start({
position: down ? [mx / 100, -my / 100, 0] : [0, 0, 0]
})
})
return (
<animated.mesh {...bind()} position={spring.position}>
<boxGeometry />
<meshStandardMaterial color="hotpink" />
</animated.mesh>
)
}
Keyboard Controls
import { KeyboardControls, useKeyboardControls } from '@react-three/drei'
const controls = [
{ name: 'forward', keys: ['ArrowUp', 'KeyW'] },
{ name: 'backward', keys: ['ArrowDown', 'KeyS'] },
{ name: 'left', keys: ['ArrowLeft', 'KeyA'] },
{ name: 'right', keys: ['ArrowRight', 'KeyD'] },
{ name: 'jump', keys: ['Space'] },
]
function App() {
return (
<KeyboardControls map={controls}>
<Canvas>
<Player />
</Canvas>
</KeyboardControls>
)
}
function Player() {
const [, get] = useKeyboardControls()
useFrame(() => {
const { forward, backward, left, right } = get()
// Move player based on keys
})
}
Scroll Controls
import { ScrollControls, useScroll, Scroll } from '@react-three/drei'
function Scene() {
return (
<ScrollControls pages={3} damping={0.1}>
<AnimatedContent />
<Scroll html>
<h1 style={{ position: 'absolute', top: '100vh' }}>Page 2</h1>
</Scroll>
</ScrollControls>
)
}
function AnimatedContent() {
const scroll = useScroll()
useFrame(() => {
const offset = scroll.offset // 0 to 1
meshRef.current.position.y = offset * 10
})
}
Html Overlay
import { Html } from '@react-three/drei'
<mesh>
<boxGeometry />
<meshStandardMaterial />
<Html position={[0, 1, 0]} center>
<div className="label">Click me!</div>
</Html>
</mesh>
Screen to World
function screenToWorld(screenX, screenY, camera, targetZ = 0) {
const vec = new THREE.Vector3(
(screenX / window.innerWidth) * 2 - 1,
-(screenY / window.innerHeight) * 2 + 1,
0.5
).unproject(camera)
const dir = vec.sub(camera.position).normalize()
const distance = (targetZ - camera.position.z) / dir.z
return camera.position.clone().add(dir.multiplyScalar(distance))
}