2022 July Update
- React Hooks
- React Function Component
- WebGLRenderer.setAnimationLoop
- Typescript
- window resizing
- WebGL context disposal when unmounted
import * as T from 'three';
import { useRef, useEffect, useCallback } from 'react';
class GL {
scene: T.Scene = new T.Scene();
renderer?: T.WebGLRenderer;
camera?: T.PerspectiveCamera;
width: number = 1;
height: number = 1;
destroyed: boolean = false;
prevTime: number = 0;
delta: number = 0;
constructor() {}
get aspect() {
return this.width / this.height;
}
init ({ canvas, width, height }: Pick<GL, 'canvas' | 'width' | 'height'>) {
this.width = width;
this.height = height;
this.camera = new T.PerspectiveCamera(70, this.aspect, 0.01, 10);
this.camera.position.z = 3;
this.scene.add(this.camera);
/* ... place your objects here ... */
this.scene.add(new T.AxisHelper(3));
this.renderer = new T.WebGLRenderer({canvas});
this.resize(width, height);
this.renderer.setAnimationLoop(this.onLoop);
}
resize(width: number, height: number) {
if (!this.camera || !this.renderer) return;
this.camera.aspect = this.aspect;
this.camera.updateProjectionMatrix();
this.renderer.setSize(width, height);
this.renderer.setPixelRatio(/* ... set your pixel ratio ... */);
}
onLoop: XRFrameRequestCallback = (time) => {
if (!this.camera || !this.renderer) return;
if (this.prevTime !== 0) this.delta = time - this.prevTime;
/* do something with your delta and time */
this.renderer.render(this.scene, this.camera);
this.prevTime = time;
};
destroy() {
this.renderer?.dispose();
this.camera = undefined;
this.destroyed = true;
}
}
let gl = new GL();
const App: React.FC = () => {
const refCanvas = useRef<HTMLCanvasElement>(null);
const onResize = useCallback(() => {
gl.resize(
refCanvas.current.clientWidth,
refCanvas.current.clientHeight
);
}, []);
useEffect(() => {
if (!refCanvas.current) return;
const state = refCanvas.current;
if (gl.destroyed) gl = new GL();
gl.init({
canvas: refCanvas.current,
width: refCanvas.current.clientWidth,
height: refCanvas.current.clientHeight,
});
window.addEventListener('resize', onResize);
return () => {
window.removeEventListener('resize', onResize);
gl.destroy();
};
}, []);
return <canvas ref={refCanvas} className="gl"></canvas>;
}