0

I'm trying to use Three.js in Typescript react, I render Dodecahedron figure and random stars, I want to add some mark up to my three.js with React but when I render Three.js canvas into HTML it dissapears my root div, and I'm not able to add some other components

THREE.JS

import * as THREE from "three";

export function ThreeCanvas() {
  const scene = new THREE.Scene();
  const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);

  const renderer = new THREE.WebGLRenderer();
  renderer.setSize(window.innerWidth, window.innerHeight);

  document.body.innerHTML = "";
  document.body.appendChild(renderer.domElement);

  const geometry = new THREE.DodecahedronBufferGeometry(1.7, 0);
  const material = new THREE.MeshStandardMaterial({
    color: "#00FF95",
  });

  const Stars = () => {
    const starGeometry = new THREE.SphereGeometry(0.1, 24, 24)
    const starMaterial = new THREE.MeshStandardMaterial({color: 0xffffff})
    const star = new THREE.Mesh(starGeometry, starMaterial)

    const [x, y, z] = Array(3).fill(1).map(() => THREE.MathUtils.randFloatSpread(70))
    star.position.set(x, y, z)

    scene.add(star)
  }

  Array(200).fill(100).forEach(Stars)

  const light = new THREE.PointLight(0xffffff)
  light.position.set(20, 20, 20)
  scene.add(light)

  camera.position.z = 5;
  camera.position.x = 3.5

  const Figure = new THREE.Mesh(geometry, material);

  scene.add(Figure);

  const animate = () => {
    requestAnimationFrame(animate);
    Figure.rotation.x += 0.01;
    Figure.rotation.y += 0.01;
    renderer.render(scene, camera);
  };

  animate();

  window.addEventListener("resize", () => {
    renderer.setSize(window.innerWidth, window.innerHeight);
    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();
  });
  
  return null
}

this is my three.js code what I do in react

React

import ReactDOM from "react-dom"
import { ThreeCanvas } from "./Components/Three";
import Landing  from "./Components/Landing";
import "./Style/Style.css"

const FirstSection = () => {
  return (
    <div className="container">
      <Landing />
      <ThreeCanvas />;
    </div>
  );
}

ReactDOM.render(<FirstSection />, document.getElementById("root"));

Landing is my markup component when I open console I dont see anywhere my Landing element but in react tools I see, how to fix that issue I have no idea

1 Answer 1

1

You're removing document.body in Three.JS component which is why the body only contains the canvas. You might want to use a reference to the element instead of targeting document.body so that it's not disturbing the DOM structure which is why your markdown does not show. As a rule of thumb, you should never be interacting with the DOM via the document.

  document.body.innerHTML = "";

I've quickly refactored the Three.JS component to use a React element reference so that you can add additional markup.

Refactored ThreeCanvas Component

import * as React from "react";
import * as THREE from "three";

export function ThreeCanvas() {
  const ref = React.useRef();
  const [loaded, setLoaded] = React.useState(false);

  React.useEffect(() => {
    if (!loaded && ref) {
      const scene = new THREE.Scene();
      const camera = new THREE.PerspectiveCamera(
        75,
        window.innerWidth / window.innerHeight,
        0.1,
        1000
      );

      const renderer = new THREE.WebGLRenderer();
      renderer.setSize(window.innerWidth, window.innerHeight);

      const geometry = new THREE.DodecahedronBufferGeometry(1.7, 0);
      const material = new THREE.MeshStandardMaterial({
        color: "#00FF95"
      });

      const Stars = () => {
        const starGeometry = new THREE.SphereGeometry(0.1, 24, 24);
        const starMaterial = new THREE.MeshStandardMaterial({
          color: 0xffffff
        });
        const star = new THREE.Mesh(starGeometry, starMaterial);

        const [x, y, z] = Array(3)
          .fill(1)
          .map(() => THREE.MathUtils.randFloatSpread(70));
        star.position.set(x, y, z);

        scene.add(star);
      };

      Array(200).fill(100).forEach(Stars);

      const light = new THREE.PointLight(0xffffff);
      light.position.set(20, 20, 20);
      scene.add(light);

      camera.position.z = 5;
      camera.position.x = 3.5;

      const Figure = new THREE.Mesh(geometry, material);

      scene.add(Figure);

      const animate = () => {
        requestAnimationFrame(animate);
        Figure.rotation.x += 0.01;
        Figure.rotation.y += 0.01;
        renderer.render(scene, camera);
      };

      const resize = () => {
        renderer.setSize(window.innerWidth, window.innerHeight);
        camera.aspect = window.innerWidth / window.innerHeight;
        camera.updateProjectionMatrix();
      };

      animate();
      ref.current.appendChild(renderer.domElement);
      window.addEventListener("resize", resize);
      setLoaded(true);
      return () => window.removeEventListener("resize", resize);
    }
  }, [ref, loaded]);

  return <div ref={ref} />;
}

export default ThreeCanvas;

Typescript Version: ThreeCanvas.tsx

import * as React from "react";
import * as THREE from "three";

export function ThreeCanvas() {
  const ref = React.useRef<HTMLDivElement>(null);
  const [loaded, setLoaded] = React.useState(false);

  React.useEffect(() => {
    if (!loaded && ref.current) {
      const scene = new THREE.Scene();
      const camera = new THREE.PerspectiveCamera(
        75,
        window.innerWidth / window.innerHeight,
        0.1,
        1000
      );

      const renderer = new THREE.WebGLRenderer();
      renderer.setSize(window.innerWidth, window.innerHeight);

      const geometry = new THREE.DodecahedronBufferGeometry(1.7, 0);
      const material = new THREE.MeshStandardMaterial({
        color: "#00FF95"
      });

      const Stars = () => {
        const starGeometry = new THREE.SphereGeometry(0.1, 24, 24);
        const starMaterial = new THREE.MeshStandardMaterial({
          color: 0xffffff
        });
        const star = new THREE.Mesh(starGeometry, starMaterial);

        const [x, y, z] = Array(3)
          .fill(1)
          .map(() => THREE.MathUtils.randFloatSpread(70));
        star.position.set(x, y, z);

        scene.add(star);
      };

      Array(200).fill(100).forEach(Stars);

      const light = new THREE.PointLight(0xffffff);
      light.position.set(20, 20, 20);
      scene.add(light);

      camera.position.z = 5;
      camera.position.x = 3.5;

      const Figure = new THREE.Mesh(geometry, material);

      scene.add(Figure);

      const animate = () => {
        requestAnimationFrame(animate);
        Figure.rotation.x += 0.01;
        Figure.rotation.y += 0.01;
        renderer.render(scene, camera);
      };

      const resize = () => {
        renderer.setSize(window.innerWidth, window.innerHeight);
        camera.aspect = window.innerWidth / window.innerHeight;
        camera.updateProjectionMatrix();
      };

      animate();
      ref.current.appendChild(renderer.domElement);
      window.addEventListener("resize", resize);
      setLoaded(true);
      return () => window.removeEventListener("resize", resize);
    }
  }, [ref, loaded]);

  return <div ref={ref} />;
}

export default ThreeCanvas;

Sign up to request clarification or add additional context in comments.

2 Comments

Thank you very much also I will add some quick fix if someone uses Typescript React, on Typescript React with this code you will get type error, Object is possibly 'undefined' on ref.current.appendChild(renderer.domElement) to fix that just write const ref = React.useRef<any>(); to manualy dissapear error
@callmenikk I've updated the answer with the correct typings for Typescript, I would recommend using that instead of setting any.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.