Reputation: 1513
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
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
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:
Upvotes: 2
Reputation: 2534
There's a few issues with your sandbox at the moment, which aren't necessarily related to React.
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.
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.
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