Files
whoami/src/ThreeViewport.tsx
T
2026-09-15 17:58:27 +02:00

69 lines
2.1 KiB
TypeScript

import { useEffect, useRef } from 'react'
import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
export type ViewportHandle = {
update?: () => void
dispose?: () => void
}
export type BuildFn = (
scene: THREE.Scene,
camera: THREE.PerspectiveCamera,
controls: OrbitControls,
renderer: THREE.WebGLRenderer,
container: HTMLDivElement,
) => ViewportHandle | void
export default function ThreeViewport({ build, className }: { build: BuildFn; className?: string }) {
const containerRef = useRef<HTMLDivElement>(null)
useEffect(() => {
const container = containerRef.current
if (!container) return
const scene = new THREE.Scene()
const camera = new THREE.PerspectiveCamera(38, container.clientWidth / container.clientHeight, 0.1, 100)
const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true })
renderer.setSize(container.clientWidth, container.clientHeight)
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
container.appendChild(renderer.domElement)
const controls = new OrbitControls(camera, renderer.domElement)
controls.enableDamping = true
controls.dampingFactor = 0.05
controls.enableZoom = false
controls.autoRotate = true
controls.autoRotateSpeed = 1
const handle = build(scene, camera, controls, renderer, container) ?? undefined
let frameId = 0
const animate = () => {
frameId = requestAnimationFrame(animate)
controls.update()
handle?.update?.()
renderer.render(scene, camera)
}
animate()
const onResize = () => {
camera.aspect = container.clientWidth / container.clientHeight
camera.updateProjectionMatrix()
renderer.setSize(container.clientWidth, container.clientHeight)
}
window.addEventListener('resize', onResize)
return () => {
cancelAnimationFrame(frameId)
window.removeEventListener('resize', onResize)
handle?.dispose?.()
controls.dispose()
renderer.dispose()
container.removeChild(renderer.domElement)
}
}, [build])
return <div ref={containerRef} className={className} />
}