jagzviruz
jagzviruz

Reputation: 1513

How to use three.js with React

I am trying to use three.js in a React application however even the basic sample from the three.js docs fails to load on the canvas.

I first made a vanilla.js sandbox implementation with the sample which works fine. Then I ported it over to create a react+three.js minimal sandbox implementation which fails to work.

Can anyone have a look at it and point me in the right direction ?

class Viewer extends Component {
    state = {};

    scene = null;

    camera = null;

    renderer = new WebGLRenderer();

    inst = 0;

    viewerRef = React.createRef();

    componentDidMount() {
        const { domElement } = this.renderer;
        this.scene = new Scene();
        this.scene.background = new Color("#ccc");
        this.camera = new Camera(
        75,
        domElement.innerWidth / domElement.innerHeight,
        0.1,
        1000
        );
        this.renderer.setSize(domElement.innerWidth, domElement.innerHeight);
        this.viewerRef.current.appendChild(this.renderer.domElement);
        const geometry = new BoxGeometry(1, 1, 1);
        const material = new MeshBasicMaterial({ color: 0x05ff00 });
        const cube = new Mesh(geometry, material);

        this.scene.add(cube);

        this.camera.position.z = 5;
        this.display();
    }

    display = () => {
        this.renderer.render(this.scene, this.camera);
        requestAnimationFrame(this.display);
    };

    render = () => <div className="viewer" ref={this.viewerRef} />;
}

Upvotes: 1

Views: 6183

Answers (3)

phoenisx
phoenisx

Reputation: 1689

I am not sure why nobody has mentioned react-three-fiber. It's a React renderer for ThreeJS.

Best part is, using rollupjs, we can even tree-shake a lot of unnecessary code, that we won't use in our React ThreeJS app. Also, using react-three-fiber in a desired way, we can achieve 60FPS for our ThreeJS animations 🥰

To learn basics, just take a look into react-three-fiber Examples

Upvotes: 2

Alexander Solovyev
Alexander Solovyev

Reputation: 581

Here is CodeSandBox that works with a basic React wrapper code for Three.js. It also has THREE.OrbitControls integration and scale on resize code:

https://codesandbox.io/s/github/supromikali/react-three-demo

Live demo: https://31yp61zxq6.codesandbox.io/

Here is a full code snippet in the case above listed links don't work for you:

index.js code

import React, { Component } from "react";
import ReactDOM from "react-dom";
import THREE from "./three";


class App extends Component {
    componentDidMount() {


        // BASIC THREE.JS THINGS: SCENE, CAMERA, RENDERER
        // Three.js Creating a scene tutorial
        // https://threejs.org/docs/index.html#manual/en/introduction/Creating-a-scene
        var scene = new THREE.Scene();
        var camera = new THREE.PerspectiveCamera(
            75,
            window.innerWidth / window.innerHeight,
            0.1,
            1000
        );
        camera.position.z = 5;

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



        // MOUNT INSIDE OF REACT
        this.mount.appendChild(renderer.domElement); // mount a scene inside of React using a ref



        // CAMERA CONTROLS
        // https://threejs.org/docs/index.html#examples/controls/OrbitControls
        this.controls = new THREE.OrbitControls(camera);



        // ADD CUBE AND LIGHTS
        // https://threejs.org/docs/index.html#api/en/geometries/BoxGeometry
        // https://threejs.org/docs/scenes/geometry-browser.html#BoxGeometry
        var geometry = new THREE.BoxGeometry(2, 2, 2);
        var material = new THREE.MeshPhongMaterial( {
            color: 0x156289,
            emissive: 0x072534,
            side: THREE.DoubleSide,
            flatShading: true
        } );
        var cube = new THREE.Mesh(geometry, material);
        scene.add(cube);

        var lights = [];
        lights[ 0 ] = new THREE.PointLight( 0xffffff, 1, 0 );
        lights[ 1 ] = new THREE.PointLight( 0xffffff, 1, 0 );
        lights[ 2 ] = new THREE.PointLight( 0xffffff, 1, 0 );

        lights[ 0 ].position.set( 0, 200, 0 );
        lights[ 1 ].position.set( 100, 200, 100 );
        lights[ 2 ].position.set( - 100, - 200, - 100 );

        scene.add( lights[ 0 ] );
        scene.add( lights[ 1 ] );
        scene.add( lights[ 2 ] );



        // SCALE ON RESIZE

        // Check "How can scene scale be preserved on resize?" section of Three.js FAQ
        // https://threejs.org/docs/index.html#manual/en/introduction/FAQ

        // code below is taken from Three.js fiddle
        // http://jsfiddle.net/Q4Jpu/

        // remember these initial values
        var tanFOV = Math.tan( ( ( Math.PI / 180 ) * camera.fov / 2 ) );
        var windowHeight = window.innerHeight;

        window.addEventListener( 'resize', onWindowResize, false );

        function onWindowResize( event ) {

            camera.aspect = window.innerWidth / window.innerHeight;

            // adjust the FOV
            camera.fov = ( 360 / Math.PI ) * Math.atan( tanFOV * ( window.innerHeight / windowHeight ) );

            camera.updateProjectionMatrix();
            camera.lookAt( scene.position );

            renderer.setSize( window.innerWidth, window.innerHeight );
            renderer.render( scene, camera );

        }



        // ANIMATE THE SCENE
        var animate = function() {
            requestAnimationFrame(animate);

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

            renderer.render(scene, camera);
        };

        animate();
    }
    render() {
        return <div ref={ref => (this.mount = ref)} />;
    }
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

three.js code (THREE.OrbitControls import)

import * as THREE from 'three';


window.THREE = THREE; // THREE.OrbitControls expects THREE to be a global object
require('three/examples/js/controls/OrbitControls');


export default {...THREE, OrbitControls: window.THREE.OrbitControls};

Result should look like this:

enter image description here

Upvotes: 2

Garrett Johnson
Garrett Johnson

Reputation: 2534

There's a few issues with your sandbox at the moment, which aren't necessarily related to React.

  1. innerWidth and innerHeight are not defined. clientWidth and clientHeight are probably the fields you're looking for to get the dimensions of the dom element.

  2. The dimensions of the canvas are being read before it has been added into the document, meaning the dimensions of the element will always be 0 0. Insert the element first before computing the camera aspect ratio and setting the render size.

  3. Camera isn't intended to be used directly. The docs specify it as an abstract base class for cameras -- use PerspectiveCamera instead.

Here's a sample of the code with the above fixes

const { domElement } = this.renderer;

// Fix 1
this.viewerRef.current.appendChild(domElement);

// Fix 2
const width = domElement.clientWidth;
const height = domElement.clientHeight;

this.scene = new Scene();
this.scene.background = new Color("#ccc");

// Fix 3
this.camera = new PerspectiveCamera(
    75,
    width  / height,
    0.1,
    1000
);
this.renderer.setSize(width, height);
const geometry = new BoxGeometry(1, 1, 1);
const material = new MeshBasicMaterial({ color: 0x05ff00 });
const cube = new Mesh(geometry, material);

this.scene.add(cube);
this.camera.position.z = 5;
this.display();

Hope that helps!

Upvotes: 0

Related Questions