みかづきブログ・カスタム

基本的にはちょちょいのほいです。

Three.js + Reactで3Dコンテンツをつくるときのためのカスタムフックをつくる 🔨

React + Three.jsでウェブサイトを作る際、こんな感じのカスタムフックを使って、WebGLRenderer、PerspectiveCamera、Sceneを取得しています。

useThree.tsx

import { useEffect, useState } from 'react';
import * as THREE from '~/build/three.module';

export default function useThree(canvas: HTMLCanvasElement | null) {
  const [ renderer, setRenderer ] = useState<THREE.WebGLRenderer>();
  const [ camera, setCamera ] = useState<THREE.PerspectiveCamera>();
  const [ scene ] = useState<THREE.Scene>(new THREE.Scene);

  useEffect(() => {
    if (!canvas) {
      return;
    }

    setRenderer(new THREE.WebGLRenderer({
      canvas,
      antialias: true,
      alpha: true
    }));
  }, [canvas]);

  useEffect(() => {
    if (!renderer) {
      return;
    }

    setCamera(new THREE.PerspectiveCamera());
  }, [renderer]);

  return { THREE, renderer, camera, scene };
}



使い方は、こんな感じです。

index.tsx

export default function IndexPage() {
  const canvasRef = useRef<HTMLCanvasElement>(null);
  const { windowWidth, windowHeight } = useResize();
  const { THREE, renderer, camera, scene } = useThree(canvasRef.current);

  useEffect(() => {
    if (!renderer || !camera) {
      return;
    }

    camera.position.set(4, 4, 8);
    camera.lookAt(new THREE.Vector3(0, 0, 0));

    const cube = new THREE.Mesh(
      new THREE.BoxGeometry(1, 1, 1),
      new THREE.MeshNormalMaterial()
    );

    scene.add(cube);

    renderer.setClearColor(0xFFFFFF, 1);
    renderer.setAnimationLoop(() => {
      renderer.render(scene, camera);
    });
  }, [renderer, camera]);

  useEffect(() => {
    if (!renderer || !camera) {
      return;
    }

    renderer.setPixelRatio(window.devicePixelRatio);
    renderer.setSize(windowWidth, windowHeight);

    camera.aspect = windowWidth / windowHeight;
    camera.updateProjectionMatrix();
  }, [renderer, camera, windowWidth, windowHeight]);

  return <canvas ref={ canvasRef } />;
}

useResizeは 以前つくったもの を流用しています。

blog.kimizuka.org