function useResponsiveCanvas( initialSize?: MinMaxPair, ): State { const canvasRef = useRef(); const mountRef = useRef(); const [size, setSize] = useState([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( new THREE.WebGLRenderer({ canvas }), ); const cameraRef = useRef( new THREE.PerspectiveCamera(viewAngle, aspect, near, far), ); const sceneRef = useRef(new THREE.Scene()); const globeRef = useRef(new THREE.Group()); const textureLoaderRef = useRef( new THREE.TextureLoader(), ); const pointLightRef = useRef( new THREE.PointLight(0xffffff), ); const animationFrameID = useRef(); // 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
; }