skip to Main Content

I´m working on a VR Web with Three.js in a React-Vite-Typescript Project.

I just start this new project so I’m codding the initial view which is a black screen with a white logo in the center (must like Rockstar Games or any company intro in movies) then all goes black and a few seconds later the THREE.JS Scene starts.

Like THREE.JS Requires an DOM element to insert the renderer.domElement. I decide to use an React Component called "ThreeCanvasContainer" which return the and div where renderer.domElement can be inserted. This is the component code.

// * Dependencies Required

import { useEffect } from 'react'

// * Modules Required

import { initThreeProcess } from './ThreeCanvasInit'

// * view Styles

import './ThreeCanvasContainer.css'

// * View To Return

const ThreeCanvasContainer = () => {

     useEffect(() => {
        initThreeProcess();            
    
    }, []);   

    return (

        <div className="ThreeJS-Canvas-Container" id='ThreeJS-Canvas-Container'></div>

    )

}

export default ThreeCanvasContainer

So as you can see the component return the DOM element with the id= ThreeJS-Canvas-Container
and after that using useEffect the initThreeProcess() function is called.
Which contains the next code.

import * as THREE from 'three';
import { VRButton } from 'three/examples/jsm/webxr/VRButton.js';

export const initThreeProcess = () => {

    // * Creating Scene & Camera

    const scene = new THREE.Scene();
    const sceneBackground = new THREE.Color(0x030613);
    const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);

    scene.background = sceneBackground
    camera.position.set(0, 1.70, 0)

    // * Creating Scene render

    const renderer = new THREE.WebGLRenderer({ antialias: true });
    renderer.setSize(window.innerWidth, window.innerHeight);

    // * Appending Element to DOM

    document.body.appendChild(VRButton.createButton(renderer));
    renderer.xr.enabled = true;

    document.getElementById('ThreeJS-Canvas-Container').appendChild(renderer.domElement);

    // * Creating Object inside Scene

    const geometry = new THREE.BoxGeometry(1, 1, 1);
    const material = new THREE.MeshBasicMaterial({ color: 0x00eabe });
    const cube = new THREE.Mesh(geometry, material);

    const edges = new THREE.EdgesGeometry(geometry);
    const edgesMaterial = new THREE.LineBasicMaterial({ color: 0xffffff });
    const edgesLines = new THREE.LineSegments(edges, edgesMaterial);

    scene.add(cube);
    scene.add(edgesLines);

    cube.position.set(0, 1.60, -2)
    edgesLines.position.set(0, 1.60, -2)

    renderer.setAnimationLoop(() => {

        cube.rotation.x += 0.01;
        cube.rotation.y += 0.01;

        edgesLines.rotation.copy(cube.rotation);

        renderer.render(scene, camera);

    });
}

So once again this code works completely fine, I´m just using the basic model that threejs give us in his Creating a scene docs plus the VRButton beein added to the DOM Body, but when I try this in development environment using npm run dev

"dev": "vite",

(And right here is where the problem shows up) For some reason the initThreeProcess function is being called twice when should only be called once. inserting two renderer.domElements inside the element that the component return displaying two scenes of three.js But when I use

"build": "tsc && vite build",

and start the server using VSCODE Extension: Live server the code works fine the initThreeProcess function is called only one time and I have no idea why.

Can somebody explain me what is going on? Can I fix this in dev env? Am I being a dumb?

2

Answers


  1. I will propose this solution to this issue, but I am not sure it is the best solution. Since in there is a double re-render of the component and a double call of useEffect, we can use this trick:

    // * Dependencies Required
    
    import { useEffect } from 'react'
    
    // * Modules Required
    
    import { initThreeProcess } from './ThreeCanvasInit'
    
    // * view Styles
    
    import './ThreeCanvasContainer.css'
    
    // * View To Return
    
    const ThreeCanvasContainer = () => {
    
         // Create an `ref` with the value `false`, which will indicate if the component is mounted
         const didLogRef = React.useRef(false); 
    
         useEffect(() => {
              if (didLogRef.current === false) {
                    // this code will only be executed once
                    didLogRef.current = true;
                    initThreeProcess();
             }      
        
        }, []);   
    
        return (
    
            <div className="ThreeJS-Canvas-Container" id='ThreeJS-Canvas-Container'></div>
    
        )
    
    }
    
    export default ThreeCanvasContainer
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search