@@ -0,0 +1,137 @@
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 } /> ;
}