Skip to content

Instantly share code, notes, and snippets.

@chrisrzhou
Created March 21, 2019 05:52
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save chrisrzhou/df1be3e9b77cad484a4fdd1ce47d28d4 to your computer and use it in GitHub Desktop.
Save chrisrzhou/df1be3e9b77cad484a4fdd1ce47d28d4 to your computer and use it in GitHub Desktop.
React Hooks + Threejs
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