👻

Nextjs,TypeScriptで3Dを表示 react-three-fiber

2023/04/11に公開

Nextjs,TypeScript を用いて react-three-fiber の
公式にあるサンプルコードを細かく解説していきます!

デモサイトです
デモサイト

準備

まずは環境構築をします。

ターミナル
// npmの場合
$ npx create-next-app --ts

// yarnの場合
$ yarn create next-app --typescript

続いてパッケージをインストールします。

ターミナル
// npmの場合
$ npm install three @types/three @react-three/fiber @types/three

// yarnの場合
$ yarn add three @types/three @react-three/fiber @types/three

実装

必要なパッケージをインストールしたので早速コードを書いていきます。

pages/index.tsx
import type { NextPage } from "next";
import React, { useRef, useState } from "react";
import * as THREE from "three";
import { Canvas, useFrame } from "@react-three/fiber";

const Home: NextPage = () => {
  const Box = (props: JSX.IntrinsicElements["mesh"]) => {
    const ref = useRef<THREE.Mesh>(null!);
    const [hovered, setHover] = useState(false);
    const [active, setActive] = useState(false);
    useFrame((state, delta) => (ref.current.rotation.x += 0.01));
    return (
      <mesh
        {...props}
        ref={ref}
        scale={active ? 1.5 : 1}
        onClick={(event) => setActive(!active)}
        onPointerOver={(event) => setHover(true)}
        onPointerOut={(event) => setHover(false)}
      >
        <boxGeometry args={[1, 1, 1]} />
        <meshStandardMaterial color={hovered ? "hotpink" : "orange"} />
      </mesh>
    );
  };

  return (
    <Canvas
      style={{
        width: 100 + "vw",
        height: 100 + "vh",
        position: "fixed",
        top: 0,
        left: 0,
      }}
    >
      <ambientLight />
      <pointLight position={[10, 10, 10]} />
      <Box position={[-1.2, 0, 0]} />
      <Box position={[1.2, 0, 0]} />
    </Canvas>
  );
};

export default Home;

解説

細かく解説していきます。

tsx
 const Box = (props: JSX.IntrinsicElements["mesh"]) => {

Box という関数コンポーネントを定義しています。
引数に props を指定し、
JSX で使用可能な HTML 要素の一覧を定義できる
IntrinsicElementsを指定し、
Three.js のMeshクラスに対する型<mesh>を指定しています。
これにより<mesh>要素のプロパティを安全に扱えるようにしています。

tsx
const ref = useRef<THREE.Mesh>(null!);

そもそもuseRef()とは
DOM 要素に直接アクセスするためのフックで
再レンダリングを発生させずに、値を保持することができます。
型引数として<THREE.Mesh>を指定し、
Mesh オブジェクトであることを明示しています。
最後のnull!は非 null アサーション演算子として
null である可能性がある値を!を使って
null でないことを示すために使用しています。

tsx
const [hovered, setHover] = useState(false);
const [active, setActive] = useState(false);

上記 2 つはuseStateフックを用いて状態を管理しています

1 つ目は hovered という状態変数を宣言して初期値は false で
ユーザーがマウスホバーしたときに true に変わりマウスを
外したときに false に変わります。

2 つ目は active という状態変数で初期値は同じく false
こちらはユーザーがマウスをクリックしている間は
true に変更され、クリックをやめたとき false になる状態変数です。

tsx
useFrame((state, delta) => (ref.current.rotation.x += 0.01));

react-three/fiber のuseFrame()フックを使って
Three.js のrequestAnimationFrameループにアクセスし、
アニメーションをループ出来るようになります。

第一引数はstateと呼ばれ、その名の通り現在の Three.js の
状態を表すオブジェクトです。

第二引数はdeltaと呼ばれ、経過時間を表します。

先程指定したrefに対して毎フレーム X 軸に 0.01 ラジアン分回転
させるアニメーションを意味しています。

tsx
 return (
      <mesh
        {...props}
        ref={ref}
        scale={active ? 1.5 : 1}
        onClick={(event) => setActive(!active)}
        onPointerOver={(event) => setHover(true)}
        onPointerOut={(event) => setHover(false)}
      >
        <boxGeometry args={[1, 1, 1]} />
        <meshStandardMaterial color={hovered ? "hotpink" : "orange"} />
      </mesh>
    );

立方体を描写するコンポーネントを書いています。

<mesh>コンポーネントは 3D オブジェクトをレンダリングするために
使用され、props として、位置、回転、スケール、ジオメトリ、マテリアル、
ライト、カメラなどが渡せます。

{...props}は先程の渡せる要素をまとめて props として
渡せるように記入しています。
このコードがない場合後ほど出てきますが、

tsx
<Box position={[-1.2, 0, 0]} />

この position の部分を mesh に props として渡さなくては
いけないですし、BOX 要素になにか付け足すたびに
props として渡さなくてはいけなくなります。

scale={}部分は active の時大きさを 1.5 倍にします。
次のonClick={(event) => setActive(!active)}
で active かどうかを指定しています。
要はマウスでクリックしている間は 1.5 倍になり、
クリックをやめると元のサイズに戻るということです。

onPointerOver={(event) => setHover(true)}
onPointerOut={(event) => setHover(false)}
はマウスをホバーしたときに状態変数の setHover を
true もしくは false に変わるようにしています。

tsx
<boxGeometry args={[1, 1, 1]} />

このコードは Thre.js の立方体のジオメトリを定義するコードです。
argsで立方体の幅、高さ、深さを指定しています。

tsx
<meshStandardMaterial color={hovered ? "hotpink" : "orange"} />

meshStandardMaterialは Three.js で提供されている標準的なマテリアルの一種です。
ここではホバーするたびに色が変わるように指定されています。

tsx
return (
    <Canvas
      style={{
        width: 100 + "vw",
        height: 100 + "vh",
        position: "fixed",
        top: 0,
        left: 0,
      }}
    >
      <ambientLight />
      <pointLight position={[10, 10, 10]} />
      <Box position={[-1.2, 0, 0]} />
      <Box position={[1.2, 0, 0]} />
    </Canvas>
  );
<Canvas
      style={{
        width: 100 + "vw",
        height: 100 + "vh",
        position: "fixed",
        top: 0,
        left: 0,
      }}
    >

<Canvas>は 3D シーンを描画するためのコンポーネントで
CSS を用いてキャンバスの高さ、横幅を指定しています。

tsx
<ambientLight />
<pointLight position={[10, 10, 10]} />

<ambientLight>は 3D シーンに影の明るさを決定する
環境光を提供しています。
このコンポートを追加することにより、オブジェクトが
より自然な見た目になります。

<pointLight position={[10, 10, 10]} />
位置(10,10,10,)に物体の立体感を表現するポイントライト
を定義しています。

tsx
<Box position={[-1.2, 0, 0]} />
<Box position={[1.2, 0, 0]} />

先程定義した Box の関数コンポーネントに
positionプロパティを指定しています。
第一引数は X 軸、第二引数は Y 軸、
第三引数は X 軸を指定できます。

2つの Box がかぶらないように
X 軸にそれぞれ-1.2,1.2を指定しています。

さいごに

おつかれさまでした!
WebGL はとても難しいですが、書けるようになったら
ブラウザ向けのウェブコンテンツの可能性が
広がるので、もっと勉強していきます。

Discussion