I have a typesafe typescript application running on next and need to implement an "old" three.js animation I built for a react project. Since typescript is a bit tricky with getting document elements i tried the following but to no surprise i keep failing at my attemts to make it work. Can anyone help? Home and AnimateCube are TSX files but Animate3D is JSX.
Here are the three scripts:
Home.tsx
import { Box, Heading } from '@chakra-ui/react';
import { ThreeSphere} from 'components/elements/Animate3D';
const Home = () => {
return (
<>
<Heading size="md" marginBottom={6} textAlign={'center'}>
Welcome!
</Heading>
<Heading size="sm" marginBottom={3} textAlign={'center'}>
Merging Centralized and DeCentralized Finance..
</Heading>
<Heading size="sm" marginBottom={8} textAlign={'center'}>
Please connect your wallet to see your Blockchain Interactions
</Heading>
<Box display={'flex'} justifyContent={'center'}>
<ThreeSphere/>
</Box>
</>
);
};
export default Home;
AnimateCube.tsx
import React, { useState } from 'react';
import Animate3D from './Animate3D.jsx';
import { Box } from '@chakra-ui/react';
const AnimateCube = () => {
const [isLoaded, setIsLoaded] = useState();
return (
<Box className="cube" minH="260px" minW="260px" boxSize="100%" objectFit="fill" onLoad={() => setIsLoaded}>
{isLoaded ? <Animate3D /> : 'Cube should be here!'}
</Box>
);
};
export default AnimateCube;
And ThreeSphere.jsx
import React, { Component } from "react";
import * as THREE from "three";
class ThreeSphere extends Component {
componentDidMount() {
const width = this.mount.clientWidth;
const height = this.mount.clientHeight;
//ADD SCENE
this.scene = new THREE.Scene();
//ADD CAMERA
this.camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 1000);
this.camera.position.z = 4;
//ADD RENDERER
this.renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
this.renderer.setClearColor(0xffffff, 0);
this.renderer.setSize(width, height);
this.mount.appendChild(this.renderer.domElement);
//ADD sphere
const geometry = new THREE.SphereGeometry(2, 8, 18);
const material = new THREE.MeshNormalMaterial({
color: 0x3ef5f5,
wireframe: true,
});
this.sphere = new THREE.Mesh(geometry, material);
this.scene.add(this.sphere);
this.start();
}
componentWillUnmount() {
this.stop();
this.mount.removeChild(this.renderer.domElement);
}
start = () => {
if (!this.frameId) {
this.frameId = requestAnimationFrame(this.animate);
}
};
stop = () => {
cancelAnimationFrame(this.frameId);
};
animate = () => {
this.sphere.rotation.x += 0.01;
this.sphere.rotation.y += 0.01;
this.renderScene();
this.frameId = window.requestAnimationFrame(this.animate);
};
renderScene = () => {
this.renderer.render(this.scene, this.camera);
};
render() {
return (
<div
style={{ width: "300px", height: "300px" }}
ref={(mount) => {
this.mount = mount;
}}
/>
);
}
}
export default ThreeSphere;
I tried making the jsx file typescript typesafe but every time I end up with document not defined even when seting type <HTMLDivElemen. . . The animation should render a spheare only made up of rainbow colored lines.
Tried
// Check if "document" exists before using it
if (typeof document === 'undefined') {
return null;
}
Tried this now but get too many linting errors I don't understand: I now tried making it typescript like this but my skills fall short on all the new Linting rules - HTMLDivElement - this. - width/height - and the request/cancelAnimatioFrame throws error:
tsx file:
import React, { Component, RefObject } from 'react';
import * as THREE from 'three';
interface Props {}
interface State {}
class ThreeSphere extends Component<Props, State> {
private mount: RefObject<HTMLDivElement> | undefined;
private scene!: THREE.Scene;
private camera!: THREE.PerspectiveCamera;
private renderer!: THREE.WebGLRenderer;
private sphere!: THREE.Mesh;
private frameId!: number;
componentDidMount() {
const width = this.mount.current?.clientWidth;
const height = this.mount.current?.clientHeight;
//ADD SCENE
this.scene = new THREE.Scene();
//ADD CAMERA
this.camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 1000);
this.camera.position.z = 4;
//ADD RENDERER
this.renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
this.renderer.setClearColor(0xffffff, 0);
this.renderer.setSize(width, height);
this.mount.current?.appendChild(this.renderer.domElement);
//ADD SPHERE
const geometry = new THREE.SphereGeometry(2, 8, 18);
const material = new THREE.MeshNormalMaterial({
color: 0x3ef5f5,
wireframe: true,
});
this.sphere = new THREE.Mesh(geometry, material);
this.scene.add(this.sphere);
this.start();
}
componentWillUnmount() {
this.stop();
this.mount.current!.removeChild(this.renderer.domElement);
}
start = () => {
if (!this.frameId) {
this.frameId = requestAnimationFrame(this.animate);
}
};
stop = () => {
cancelAnimationFrame(this.frameId);
};
animate = () => {
this.sphere.rotation.x += 0.01;
this.sphere.rotation.y += 0.01;
this.renderScene();
this.frameId = requestAnimationFrame(this.animate);
};
renderScene = () => {
this.renderer.render(this.scene, this.camera);
};
render() {
return (
<div
style={{ width: '300px', height: '300px' }}
ref={(mount) => {
this.mount = mount;
}}
/>
);
}
}
export default ThreeSphere;