Created
March 21, 2019 05:52
-
-
Save chrisrzhou/df1be3e9b77cad484a4fdd1ce47d28d4 to your computer and use it in GitHub Desktop.
React Hooks + Threejs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function useResponsiveCanvas<T>( | |
initialSize?: MinMaxPair, | |
): State { | |
const canvasRef = useRef<HTMLCanvasElement>(); | |
const mountRef = useRef<HTMLDivElement>(); | |
const [size, setSize] = useState<MinMaxPair>([0, 0]); | |
// set initial svg and size | |
useEffect(() => { | |
const canvas = document.createElement('canvas'); | |
const mount = mountRef.current; | |
canvas.style.display = 'block'; | |
canvasRef.current = canvas; | |
// update initial size | |
let width = 0; | |
let height = 0; | |
if (initialSize !== undefined) { | |
// Use initialSize if it is provided | |
[width, height] = initialSize; | |
} else { | |
// Use parentElement size if resized has not updated | |
width = mount.offsetWidth; | |
height = mount.offsetHeight; | |
} | |
setSize([width, height]); | |
// update resize using a resize observer | |
const resizeObserver = new ResizeObserver(entries => { | |
if (!entries || !entries.length) { | |
return; | |
} | |
if (initialSize === undefined) { | |
let { width, height } = entries[0].contentRect; | |
setSize([width, height]); | |
} | |
}); | |
resizeObserver.observe(mount); | |
mount.appendChild(canvas); | |
// cleanup | |
return () => { | |
resizeObserver.unobserve(mount); | |
mount.removeChild(canvas); | |
}; | |
}, [initialSize]); | |
return { | |
canvas: canvasRef.current, | |
mountRef, | |
size, | |
}; | |
} | |
function ReactGlobe({ size: initialSize }: Props): React.ReactElement { | |
const { canvas, mountRef, size } = useResponsiveCanvas(initialSize); | |
const [width, height] = size; | |
const aspect = 1; | |
const viewAngle = 45; | |
const near = 0.1; | |
const far = 10000; | |
const rendererRef = useRef<THREE.WebGLRenderer>( | |
new THREE.WebGLRenderer({ canvas }), | |
); | |
const cameraRef = useRef<THREE.PerspectiveCamera>( | |
new THREE.PerspectiveCamera(viewAngle, aspect, near, far), | |
); | |
const sceneRef = useRef<THREE.Scene>(new THREE.Scene()); | |
const globeRef = useRef<THREE.Group>(new THREE.Group()); | |
const textureLoaderRef = useRef<THREE.TextureLoader>( | |
new THREE.TextureLoader(), | |
); | |
const pointLightRef = useRef<THREE.PointLight>( | |
new THREE.PointLight(0xffffff), | |
); | |
const animationFrameID = useRef<number>(); | |
// init globe | |
useEffect(() => { | |
// get current instances | |
const mount = mountRef.current; | |
const renderer = rendererRef.current; | |
const camera = cameraRef.current; | |
const scene = sceneRef.current; | |
const globe = globeRef.current; | |
const textureLoader = textureLoaderRef.current; | |
const pointLight = pointLightRef.current; | |
//set update function to transform the scene and view | |
function animate(): void { | |
renderer.render(scene, camera); | |
requestAnimationFrame(animate); | |
} | |
const RADIUS = 300; | |
const SEGMENTS = 50; | |
const RINGS = 50; | |
// build globe | |
textureLoader.load( | |
'https://raw.githubusercontent.com/mrdoob/three.js/e7ff8ca1be184316132f28a7c48d6bfdf26e2db0/examples/textures/land_ocean_ice_cloud_2048.jpg', | |
function(map) { | |
const sphere = new THREE.SphereGeometry(RADIUS, SEGMENTS, RINGS); | |
const material = new THREE.MeshBasicMaterial({ | |
map, | |
}); | |
const mesh = new THREE.Mesh(sphere, material); | |
globe.add(mesh); | |
}, | |
); | |
globe.position.z = -RADIUS; | |
// position light and camera | |
pointLight.position.x = 10; | |
pointLight.position.y = 50; | |
pointLight.position.z = 400; | |
camera.position.set(0, 0, 500); | |
// update scene | |
scene.add(camera); | |
scene.add(globe); | |
scene.add(pointLight); | |
// mount element and animate | |
mount.appendChild(renderer.domElement); | |
animate(); | |
}, [mountRef]); | |
// update size | |
useEffect(() => { | |
const renderer = rendererRef.current; | |
renderer.setSize(width, height); | |
}, [height, width]); | |
return <div style={{ height: '100%', width: '100%' }} ref={mountRef} />; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment