Skip to content

Instantly share code, notes, and snippets.

@aquiseb
Last active May 14, 2020 20:09
Show Gist options
  • Save aquiseb/c97b05bd4e8d89b2558b1c2a5a955248 to your computer and use it in GitHub Desktop.
Save aquiseb/c97b05bd4e8d89b2558b1c2a5a955248 to your computer and use it in GitHub Desktop.

Three.js React Hooks

This example shows how to use animate a cube and highly reuse each part of Three.js.

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

// WORLD SIZE
// ---
const defineWorldSize = (renderer) => {
	renderer.setSize(window.innerWidth, window.innerHeight);
};

// WORLD RESIZE
// ---
const useWorldResizer = ({ renderer, camera }) => {
	const resizer = () => {
		camera.aspect = window.innerWidth / window.innerHeight;
		camera.updateProjectionMatrix();
		defineWorldSize(renderer);
	};

	window.addEventListener("resize", resizer, false);
};

// WORLD CREATION
// ---
const useWorld = () => {
	// RENDERER
	// ---
	const renderer = new THREE.WebGLRenderer({ antialias: true });
	renderer.setPixelRatio(window.devicePixelRatio);
	renderer.setClearColor("#ffffff");

	// SCENE
	// ---
	const scene = new THREE.Scene();
	scene.background = new THREE.Color(0xcccccc);
	// scene.fog = new THREE.Fog(0x000000, 4, 12);

	// CAMERA
	// ---
	const camera = new THREE.PerspectiveCamera(35, window.innerWidth / window.innerHeight, 1, 1000);
	camera.position.set(0, 0, 8);

	// LIGHT
	// ---
	const light = new THREE.PointLight(0xffffff, 2, 0, 2);
	light.position.set(100, 100, 100);
	scene.add(light);

	return { renderer, scene, camera };
};

// OBJECT CUBE
// ---
const useDemoCube = (scene) => {
	const geometry = new THREE.BoxGeometry(1, 1, 1);
	const material = new THREE.MeshLambertMaterial({ color: 0xff385c });
	const cube = new THREE.Mesh(geometry, material);
	scene.add(cube);

	return { cube };
};

// ANIMATION ROTATION
// ---
const animationRotation = (obj) => {
	(obj.rotation || {}).x += 0.01;
	(obj.rotation || {}).y += 0.01;
};

// ANIMATION RENDER
// --
const animationRender = (animation, loadedRenderer, requestRef) => {
	animation();
	if (typeof loadedRenderer === "function") loadedRenderer();
	const renderedAnimation = () => animationRender(animation, loadedRenderer, requestRef);
	const frameId = requestAnimationFrame(renderedAnimation);
	requestRef.current = frameId;
};

const animationStop = (requestRef) => {
	cancelAnimationFrame(requestRef.current);
};

const useThree = (threeRef, requestRef) => {
	// Get our renderer and other elements of our world
	const { renderer, scene, camera } = useWorld();

	// Define the size of our 3D world$
	defineWorldSize(renderer);

	// Use the resizer, for when our window gets resized
	useWorldResizer({ renderer, camera });

	// Create a simple cube
	const { cube } = useDemoCube(scene);

	// Define our animation
	const animation = () => animationRotation(cube);

	// Define our renderer
	const loadedRenderer = () => renderer.render(scene, camera);

	// Render our animation
	animationRender(animation, loadedRenderer, requestRef);

	// Append our renderer to the DOM
	threeRef.current.append(renderer.domElement);
};

const ThreeComponent = () => {
	const threeRef = React.useRef(null);
	// * DO NOT USE useState to get the frameId - https://css-tricks.com/using-requestanimationframe-with-react-hooks/
	const requestRef = React.useRef();

	React.useEffect(() => {
		useThree(threeRef, requestRef);
		return () => animationStop(requestRef);
	}, []);

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

export default ThreeComponent;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment