見出し画像

CodePenでesm.sh&CDNでお手軽にReact Three FiberとReact RouterとTailwind CSSを連携させてみるテスト


概要

前回からの継続作業です。 目標だったReact Three FiberとReact RouterとTailwind CSSの連携を行いました。 まず最初に前回の作業からReact Three Fiberのバージョンを同じ8.15.12にしたままTailwind CSSを追加した作業をしばらく行い、Tailwind CSSとの連携がうまくいくことを確認後、React Three Fiberのバージョンを8.16.1に上げて作業を行いました。 React Three Fiberのバージョンを8.16.1に上げたのは使用するreact-three/dreiのバージョンに合わせるためのものです。 

ちなみにReact Three Fiber、React RouterとTailwind CSSの連携を目指したのは主に以下の理由からです。

■①■
素のThree.jsでは画面切り替えのたびに、切り替え元の画面の3Dオブジェクトを全てremoveしてから、さらに遷移先の画面では表示する3Dオブジェクトを全てaddし直す、という作業が発生していましたが(←Scene機能なんかをうまく使えば省略できる?)、React Three FiberとReact Routerを使用することでそれが省略できないかと試してみました。 結果は、作業を省略することができました。
■②■
毎回ボタンや文字表示等のレイアウト作成で悩むCSSの設定を、話題の?流行りの?Tailwind CSSの使用で簡略化できないかと目論みました。 結局Tailwind CSSの使い方も散々調べて悩みしたがw、Tailwind CSSのタグの機能をある程度覚えたら、または作業を記録をしていったら作業効率高まるのかも?、とも思えました。 CSSファイルとの往復もなくなりました。 CodPenを使用というのあるのでしょうが、CodeSandboxでのMeterial UI使用時にくらべればかなり処理も軽くなりました。 

CodePenのコードではReact Routerで3画面を切り替えているので、そろそろ画面ごとのファイルに切り分けるたほうがようさそうですが、CodePen使用のためひとつのJavaScriptファイルにまとめてしまっています。 CodePenにもexportの機能などがあるみたいなので、それを使いこなせばうまく画面ごとのファイルのように切り分けられるかもですが、とりあえず保留にしています。

また、React Three FiberとReact RouterとTailwind CSSの連携ではさすがに最初の読み込み対象が多くなってしまったのか、たまにそれらを読み込みきれずに?実行画面が真っ白になってしまうことが発生するようになってしまいました。 そのときはReloadしてうまくそれらを読み込み直すことができれば対象コードの実行画面を再表示することができると思われます。 例外的に、useRef()を使わなかったためか「失敗」になってしまっている、React Three Fiber 8.16.1, React Router 6.22.3 & Tailwind CSS 3.4.3 Test06(失敗)、では1度表示されなくなった3Dオブジェクトの表示画面は、ずっと黒い背景の3Dオブジェクト非表示画面のままとなります。 追記:そのTest06から修正したTest06_03では他のコードと同じように表示される状態になりました。

同じ理由で、上記の3機能の連携からか、作業中たまに自分のオンボロPCがかなり熱くなるときがあります。 たぶんオンボロPCのせいだと思いますが、もしかしたら自分の実験的React Three Fiberコードの何かが悪いのかも? 失敗した失敗した失敗した、、、(シュタゲ風)、でないことを祈りますw。

コードはReact Three Fiber 8.16.1, React Router 6.22.3 & Tailwind CSS 3.4.3以降の項目では全てのコードを載せてみました(失敗したTest06では修正が完了したTest06_03のみコード表示)。 おかげでこのnoteがメッチャ長くなってしまいましたがw、自分の参照しやすい作業補助用のサムネイル付きコードリストとしてそのまま公開しています。

以下作成したコードのリストとなります。 各コードにて、タイトル文字によるリンクはコード付きCodePenへのリンク、サムネイル画像によるリンクはコードなしのコード実行結果の全画面表示へのリンク、となります

React Three Fiber 8.15.12, React Router 6.22.3 & Tailwind CSS

React Three Fiber 8.15.12, React Router 6.22.3 & Tailwind CSS Test01

React Three Fiber 8.15.12, React Router 6.22.3 & Tailwind CSS Test01

初期画面
「Box」クリック時
「Plane」クリック時
「Sphere」クリック時

参考にさせていただいたReact Router Active NavLink with Tailwind CSSから派生させてもらいました。 派生させた自分のコードからおそるおそる変更を加えていっていますw。

Tailwind CSSはCodePenのCSSインクルード機能で、
https://cdnjs.cloudflare.com/ajax/libs/tailwindcss/2.0.2/tailwind.min.css
を使用しています。
CodePenのCSSやJSのインクルード機能を使い、Tailwind CSSのバージョンを変えつつ、できるだけ新しめのTailwind CSSが使える方式を探りながら、しばらく作業を続けました。

Tailwind CSSの機能は参考にさせていただいたコードのまま「Box」「Plane」「Sphere」の文字をクリックして選択した場合に太字に変化するものを使用しています。


React Three Fiber 8.15.12, React Router 6.22.3 & Tailwind CSS Test02

React Three Fiber 8.15.12, React Router 6.22.3 & Tailwind CSS Test02

「Box」クリック時
「Plane」クリック時
「Sphere」クリック時

ひきつづきCodePenのCSSインクルード機能で、https://cdnjs.cloudflare.com/ajax/libs/tailwindcss/2.0.2/tailwind.min.css
を使用しています。
Tailwind CSSにより「Box」「Plane」「Sphere」を選択クリック時にテキストの背景は青になり、テキストは白抜きになるようにしました。


React Three Fiber 8.15.12, React Router 6.22.3 & Tailwind CSS Test03

React Three Fiber 8.15.12, React Router 6.22.3 & Tailwind CSS Test03

Test02から何を変更したか思い出せませんw。


React Three Fiber 8.15.12, React Router 6.22.3 & Tailwind CSS Test04

React Three Fiber 8.15.12, React Router 6.22.3 & Tailwind CSS Test04

「Box」クリック時
「Plane」クリック時
「Sphere」クリック時

CodePenのJSインクルード機能で、
https://cdn.tailwindcss.com/3.4.3
を使用するように変更しました。
ここまでで、いちおうReact Three FiberとReact RouterとTailwind CSSの連携ができることを確認して、新しそうなTailwind CSSのバージョン3.X.Xの使い方も確かめられました。


ここまでのコード作成のために参考にさせていただいたサイトその1

React RouterのNavLinkにTailwindCSSを適応させるために以下のサイトを参考にさせていただきました
React Router Active NavLink with Tailwind CSS

CodePenでTailwindCSSをインクルードするにあたって以下のサイト・コードを参考にさせていただきました(作業への動機付け含みます)
Checkout Widget - TailwindCSS
Stripe-style animated vertical tabs
Data Card
Tailwind CSS Example
TailwindCSS
Tailwind CSS Playground
Tailwind Example
Simple Tailwind CSS starter
Start Tailwind CSS - Template

tailwind
Tailwind CSS


React Three Fiber 8.16.1, React Router 6.22.3 & Tailwind CSS 3.4.3

以下のコードよりReact Three Fiber のバージョン8.16.1を使用しています。 react-three/dreiを使用するために、自分のReact Three Fiberコードで動いた実績のある バージョン8.16.1に揃えました。

React Three Fiber 8.16.1, React Router 6.22.3 & Tailwind CSS 3.4.3 Test01

React Three Fiber 8.16.1, React Router 6.22.3 & Tailwind CSS 3.4.3 Test01

「Box」クリック時
「Plane」クリック時
「Sphere」クリック時

ひとつ前のコードReact Three Fiber 8.15.12, React Router 6.22.3 & Tailwind CSS Test04からReact Three Fiberをバージョン8.16.1に変更したものです。 ここからTailwind CSSもバージョンを3.4.3に固定して作業を行っています。

HTML

<div id="root"></div>

CSS なし

JS(JavaScript)

import React, { useRef, useState } from 'https://esm.sh/react@18.2.0'
import ReactDOM from "https://esm.sh/react-dom@18.2.0";
import {
  BrowserRouter,
  NavLink,
  Routes,
  Route
} from "https://esm.sh/react-router-dom@6.22.3";

import { Canvas, useFrame } from 'https://esm.sh/@react-three/fiber@8.16.1'
import { DoubleSide } from 'https://esm.sh/three@0.163.0'

const App = () => {

  const activeLink = "bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded";

  return (
    <>

      <nav className="container flex justify-around py-8 mx-auto bg-white">

        <div>
          <h3 className="text-2xl font-medium text-blue-500">LOGO</h3>
        </div>
      
        <div className="space-x-8">
          <NavLink
            to="/"
            className={({ isActive }) =>
              isActive ? activeLink : ""
            }
          >
            Box
          </NavLink>

          <NavLink
            to="/plane"
            className={({ isActive }) =>
              isActive ? activeLink : ""
            }
          >
            Plane
          </NavLink>

          <NavLink
            to="/sphere"
            className={({ isActive }) =>
              isActive ? activeLink : ""
            }
          >
            Sphere
          </NavLink>
        </div>
      </nav>

      <Canvas>

        <ambientLight intensity={Math.PI / 2} />
        <spotLight position={[10, 10, 10]} angle={0.15} penumbra={1} decay={0} intensity={Math.PI} />
        <pointLight position={[-10, -10, -10]} decay={0} intensity={Math.PI} />

        <Routes>

          <Route path="/" element={<Box position={[0, 0, 2.5]} />}></Route>
          <Route path="/plane" element={<Plane position={[0, 0, 0]} />}></Route>
          <Route path="/sphere" element={<Sphere position={[0, 0, 0]} />}></Route>

        </Routes>

      </Canvas>



    </>
  );
}

// Box Start /////////////////////////////////////////////////////////////////////////////
const Box = (props) => {
  const meshRef = useRef();
  const [hovered, setHover] = useState(false)
  const [active, setActive] = useState(false)
  useFrame((state, delta) => (meshRef.current.rotation.x += delta))
  return (
    <mesh
      {...props}
      ref={meshRef}
      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>
  )
}
// Box End ///////////////////////////////////////////////////////////////////////////////

// Plane Start ///////////////////////////////////////////////////////////////////////////
const Plane = (props) => {
  const meshRef = useRef();
  const [hovered, setHover] = useState(false)
  const [active, setActive] = useState(false)

  useFrame((state) => {
    meshRef.current.rotation.x = state.mouse.x * 5
    meshRef.current.rotation.y = state.mouse.y * 5
  }, [])

  return (
    <mesh
      {...props}
      ref={meshRef}
      scale={active ? 1.5 : 1}
      onClick={(event) => setActive(!active)}
      onPointerOver={(event) => setHover(true)}
      onPointerOut={(event) => setHover(false)}>
      <planeGeometry args={[2, 2, 2]} />
      <meshStandardMaterial color={hovered ? 'red' : 'blue'} side={DoubleSide} />
    </mesh>
  )
}
// Plane End /////////////////////////////////////////////////////////////////////////////

// Sphere Start //////////////////////////////////////////////////////////////////////////
const Sphere = (props) => {
  const meshRef = useRef();
  const [hovered, setHover] = useState(false)
  const [active, setActive] = useState(false)

  useFrame((state) => {
    meshRef.current.rotation.x = state.mouse.x * 5
    meshRef.current.rotation.y = state.mouse.y * 5
  }, [])

  return (
    <mesh
      {...props}
      ref={meshRef}
      scale={active ? 1.5 : 1}
      onClick={(event) => setActive(!active)}
      onPointerOver={(event) => setHover(true)}
      onPointerOut={(event) => setHover(false)}>
      <sphereGeometry args={[2, 25, 25]} />
      <meshStandardMaterial wireframe color={hovered ? 'red' : 'blue'} />
    </mesh>
  )
}
// Sphere End ////////////////////////////////////////////////////////////////////////////

ReactDOM.render(
  <BrowserRouter>
    <App />
  </BrowserRouter>,
  document.getElementById("root")
);


React Three Fiber 8.16.1, React Router 6.22.3 & Tailwind CSS 3.4.3 Test02

React Three Fiber 8.16.1, React Router 6.22.3 & Tailwind CSS 3.4.3 Test02

「Box」クリック時
「Plane」クリック時
「Sphere」クリック時

Test01から、React Three FiberのCanvas内に文字列を表示させるように変更しています。

HTML

<div id="root"></div>

CSS なし

JS(JavaScript)

import React, { useRef, useState } from 'https://esm.sh/react@18.2.0'
import ReactDOM from "https://esm.sh/react-dom@18.2.0";
import {
  BrowserRouter,
  NavLink,
  Routes,
  Route
} from "https://esm.sh/react-router-dom@6.22.3";

import { Canvas, useFrame } from 'https://esm.sh/@react-three/fiber@8.16.1'
import { Html } from 'https://esm.sh/@react-three/drei@9.105.4'
import { DoubleSide } from 'https://esm.sh/three@0.163.0'

const App = () => {

  const activeLink = "bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded";

  return (
    <>

      <nav className="container flex justify-around py-8 mx-auto bg-white">

        <div>
          <h3 className="text-2xl font-medium text-blue-500">LOGO</h3>
        </div>
      
        <div className="space-x-8">
          <NavLink
            to="/"
            className={({ isActive }) =>
              isActive ? activeLink : ""
            }
          >
            Box
          </NavLink>

          <NavLink
            to="/plane"
            className={({ isActive }) =>
              isActive ? activeLink : ""
            }
          >
            Plane
          </NavLink>

          <NavLink
            to="/sphere"
            className={({ isActive }) =>
              isActive ? activeLink : ""
            }
          >
            Sphere
          </NavLink>
        </div>
      </nav>

      <Canvas>

        <ambientLight intensity={Math.PI / 2} />
        <spotLight position={[10, 10, 10]} angle={0.15} penumbra={1} decay={0} intensity={Math.PI} />
        <pointLight position={[-10, -10, -10]} decay={0} intensity={Math.PI} />

        <Routes>

          <Route path="/" element={<Box position={[0, 0, 2.5]} />}></Route>
          <Route path="/plane" element={<Plane position={[0, 0, 0]} />}></Route>
          <Route path="/sphere" element={<Sphere position={[0, 0, 0]} />}></Route>

        </Routes>

      </Canvas>



    </>
  );
}

// Box Start /////////////////////////////////////////////////////////////////////////////
const Box = (props) => {
  const meshRef = useRef();
  const [hovered, setHover] = useState(false)
  const [active, setActive] = useState(false)
  useFrame((state, delta) => (meshRef.current.rotation.x += delta))

  return (
    <>

      <Html fullscreen>
        <h3 className="text-center text-2xl font-medium text-blue-500">Hello World! This is 3D Cube Objec!!</h3>
      </Html>

      <mesh
        {...props}
        ref={meshRef}
        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 ? 'red' : 'blue'} />
      </mesh>

    </>
  )

}
// Box End ///////////////////////////////////////////////////////////////////////////////

// Plane Start ///////////////////////////////////////////////////////////////////////////
const Plane = (props) => {
  const meshRef = useRef();
  const [hovered, setHover] = useState(false)
  const [active, setActive] = useState(false)

  useFrame((state) => {
    meshRef.current.rotation.x = state.mouse.x * 5
    meshRef.current.rotation.y = state.mouse.y * 5
  }, [])

  return (
    <>

      <Html fullscreen>
        <h3 className="text-center text-2xl font-medium text-red-500">Hello World! This is 3D Plane Objec!!</h3>
      </Html>

      <mesh
        {...props}
        ref={meshRef}
        scale={active ? 1.5 : 1}
        onClick={(event) => setActive(!active)}
        onPointerOver={(event) => setHover(true)}
        onPointerOut={(event) => setHover(false)}>
        <planeGeometry args={[2, 2, 2]} />
        <meshStandardMaterial color={hovered ? 'red' : 'blue'} side={DoubleSide} />
      </mesh>

    </>
  )

}
// Plane End /////////////////////////////////////////////////////////////////////////////

// Sphere Start //////////////////////////////////////////////////////////////////////////
const Sphere = (props) => {
  const meshRef = useRef();
  const [hovered, setHover] = useState(false)
  const [active, setActive] = useState(false)

  useFrame((state) => {
    meshRef.current.rotation.x = state.mouse.x * 5
    meshRef.current.rotation.y = state.mouse.y * 5
  }, [])

  return (
    <>

      <Html fullscreen>
        <h3 className="text-center text-2xl font-medium text-black-500">Hello World! This is 3D Sphere Objec!!</h3>
      </Html>

      <mesh
        {...props}
        ref={meshRef}
        scale={active ? 1.5 : 1}
        onClick={(event) => setActive(!active)}
        onPointerOver={(event) => setHover(true)}
        onPointerOut={(event) => setHover(false)}>
        <sphereGeometry args={[2, 25, 25]} />
        <meshStandardMaterial wireframe color={hovered ? 'red' : 'blue'} />
      </mesh>

    </>
  )


}
// Sphere End ////////////////////////////////////////////////////////////////////////////

ReactDOM.render(
  <BrowserRouter>
    <App />
  </BrowserRouter>,
  document.getElementById("root")
);


React Three Fiber 8.16.1, React Router 6.22.3 & Tailwind CSS 3.4.3 Test03

React Three Fiber 8.16.1, React Router 6.22.3 & Tailwind CSS 3.4.3 Test03

「Box」クリック時
「Plane」クリック時
「Sphere」クリック時

Test02からCanvas部分が大きく表示されるように修正しています。 CSSで指定していたwidth・heightを100%に設定、的な指定をTailwind CSSで行うよう修正しました。

HTML

<div id="root"></div>

CSS なし

JS(JavaScript)

import React, { useRef, useState } from 'https://esm.sh/react@18.2.0'
import ReactDOM from "https://esm.sh/react-dom@18.2.0";
import {
  BrowserRouter,
  NavLink,
  Routes,
  Route
} from "https://esm.sh/react-router-dom@6.22.3";

import { Canvas, useFrame } from 'https://esm.sh/@react-three/fiber@8.16.1'
import { Html } from 'https://esm.sh/@react-three/drei@9.105.4'
import { DoubleSide } from 'https://esm.sh/three@0.163.0'

const App = () => {

  const activeLink = "bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded";

  return (
    <>

      <nav className="container flex justify-around py-8 mx-auto bg-white">

        <div>
          <h3 className="text-2xl font-medium text-blue-500">LOGO</h3>
        </div>
      
        <div className="space-x-8">
          <NavLink
            to="/"
            className={({ isActive }) =>
              isActive ? activeLink : ""
            }
          >
            Box
          </NavLink>

          <NavLink
            to="/plane"
            className={({ isActive }) =>
              isActive ? activeLink : ""
            }
          >
            Plane
          </NavLink>

          <NavLink
            to="/sphere"
            className={({ isActive }) =>
              isActive ? activeLink : ""
            }
          >
            Sphere
          </NavLink>
        </div>
      </nav>


      <div className="w-full h-screen">

        <Canvas>

          <ambientLight intensity={Math.PI / 2} />
          <spotLight position={[10, 10, 10]} angle={0.15} penumbra={1} decay={0} intensity={Math.PI} />
          <pointLight position={[-10, -10, -10]} decay={0} intensity={Math.PI} />

          <Routes>

            <Route path="/" element={<Box position={[0, 0, 2.5]} />}></Route>
            <Route path="/plane" element={<Plane position={[0, 0, 0]} />}></Route>
            <Route path="/sphere" element={<Sphere position={[0, 0, 0]} />}></Route>

          </Routes>

        </Canvas>

      </div>


    </>
  );
}

// Box Start /////////////////////////////////////////////////////////////////////////////
const Box = (props) => {
  const meshRef = useRef();
  const [hovered, setHover] = useState(false)
  const [active, setActive] = useState(false)
  useFrame((state, delta) => (meshRef.current.rotation.x += delta))

  return (
    <>

      <Html fullscreen>
        <h3 className="text-center text-2xl font-medium text-blue-500">Hello World! This is 3D Cube Objec!!</h3>
      </Html>

      <mesh
        {...props}
        ref={meshRef}
        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 ? 'red' : 'blue'} />
      </mesh>

    </>
  )

}
// Box End ///////////////////////////////////////////////////////////////////////////////

// Plane Start ///////////////////////////////////////////////////////////////////////////
const Plane = (props) => {
  const meshRef = useRef();
  const [hovered, setHover] = useState(false)
  const [active, setActive] = useState(false)

  useFrame((state) => {
    meshRef.current.rotation.x = state.mouse.x * 5
    meshRef.current.rotation.y = state.mouse.y * 5
  }, [])

  return (
    <>

      <Html fullscreen>
        <h3 className="text-center text-2xl font-medium text-red-500">Hello World! This is 3D Plane Objec!!</h3>
      </Html>

      <mesh
        {...props}
        ref={meshRef}
        scale={active ? 1.5 : 1}
        onClick={(event) => setActive(!active)}
        onPointerOver={(event) => setHover(true)}
        onPointerOut={(event) => setHover(false)}>
        <planeGeometry args={[2, 2, 2]} />
        <meshStandardMaterial color={hovered ? 'red' : 'blue'} side={DoubleSide} />
      </mesh>

    </>
  )

}
// Plane End /////////////////////////////////////////////////////////////////////////////

// Sphere Start //////////////////////////////////////////////////////////////////////////
const Sphere = (props) => {
  const meshRef = useRef();
  const [hovered, setHover] = useState(false)
  const [active, setActive] = useState(false)

  useFrame((state) => {
    meshRef.current.rotation.x = state.mouse.x * 5
    meshRef.current.rotation.y = state.mouse.y * 5
  }, [])

  return (
    <>

      <Html fullscreen>
        <h3 className="text-center text-2xl font-medium text-black-500">Hello World! This is 3D Sphere Objec!!</h3>
      </Html>

      <mesh
        {...props}
        ref={meshRef}
        scale={active ? 1.5 : 1}
        onClick={(event) => setActive(!active)}
        onPointerOver={(event) => setHover(true)}
        onPointerOut={(event) => setHover(false)}>
        <sphereGeometry args={[2, 25, 25]} />
        <meshStandardMaterial wireframe color={hovered ? 'red' : 'blue'} />
      </mesh>

    </>
  )

}
// Sphere End ////////////////////////////////////////////////////////////////////////////

ReactDOM.render(
  <BrowserRouter>
    <App />
  </BrowserRouter>,
  document.getElementById("root")
);


React Three Fiber 8.16.1, React Router 6.22.3 & Tailwind CSS 3.4.3 Test04

React Three Fiber 8.16.1, React Router 6.22.3 & Tailwind CSS 3.4.3 Test04

「Box」クリック時
「Plane」クリック時
「Sphere」クリック時

「up」「left」「right」「down」といったTailwind CSSによるボタンを追加して、ボタンを押すとBox、Plane、Sphereといった3Dオブジェクトが回転するようにしています。 Sphereの画面では指定した領域を半透明の緑色で塗りつぶしたらどのようになるか実験しています。 同じコンポーネント内にあるdreiの<Html、、、やTailwind CSSによる半透明の緑色の領域やボタンは、同コンポーネント内のReact Three Fiberオブジェクトの上に描画されるようでした。 また「Box」「Plane」「Sphere」の画面ごとにボタンの配置の仕方を微妙に変えてTailwind CSSによるレイアウトの指定をいろいろ試しています。

HTML

<div id="root"></div>

CSS なし

JS(JavaScript)

import React, { useRef, useState } from 'https://esm.sh/react@18.2.0'
import ReactDOM from "https://esm.sh/react-dom@18.2.0";
import {
  BrowserRouter,
  NavLink,
  Routes,
  Route
} from "https://esm.sh/react-router-dom@6.22.3";

import { Canvas, useFrame } from 'https://esm.sh/@react-three/fiber@8.16.1'
import { Html } from 'https://esm.sh/@react-three/drei@9.105.4'
import { DoubleSide } from 'https://esm.sh/three@0.163.0'

const App = () => {

  const activeLink = "bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded";

  return (
    <>

      <nav className="container flex justify-around py-8 mx-auto bg-white">

        <div>
          <h3 className="text-2xl font-medium text-blue-500">LOGO</h3>
        </div>
      
        <div className="space-x-8">
          <NavLink
            to="/"
            className={({ isActive }) =>
              isActive ? activeLink : ""
            }
          >
            Box
          </NavLink>

          <NavLink
            to="/plane"
            className={({ isActive }) =>
              isActive ? activeLink : ""
            }
          >
            Plane
          </NavLink>

          <NavLink
            to="/sphere"
            className={({ isActive }) =>
              isActive ? activeLink : ""
            }
          >
            Sphere
          </NavLink>
        </div>
      </nav>


      <div className="w-full h-screen">

        <Canvas>

          <ambientLight intensity={Math.PI / 2} />
          <spotLight position={[10, 10, 10]} angle={0.15} penumbra={1} decay={0} intensity={Math.PI} />
          <pointLight position={[-10, -10, -10]} decay={0} intensity={Math.PI} />

          <Routes>

            <Route path="/" element={<Box position={[0, 0, 2.5]} />}></Route>
            <Route path="/plane" element={<Plane position={[0, 0, 0]} />}></Route>
            <Route path="/sphere" element={<Sphere position={[0, 0, 0]} />}></Route>

          </Routes>

        </Canvas>

      </div>


    </>
  );
}

// Box Start /////////////////////////////////////////////////////////////////////////////
const Box = (props) => {
  const meshRef = useRef();
  const [hovered, setHover] = useState(false)
  const [active, setActive] = useState(false)

  const [up, toggleUp] = useState(false)
  const [left, toggleLeft] = useState(false)
  const [right, toggleRight] = useState(false)
  const [down, toggleDown] = useState(false)

  useFrame((state) => {
    meshRef.current.rotation.x += up ? -0.1 : 0
    meshRef.current.rotation.x += down ? 0.1 : 0
    meshRef.current.rotation.y += left ? -0.1 : 0
    meshRef.current.rotation.y += right ? 0.1 : 0
  }, [])

  return (
    <>

      <Html fullscreen>

        <h3 className="text-center text-2xl font-medium text-blue-500">Hello World! This is 3D Cube Objec!!</h3>

        <div class="flex justify-center">

          <div class="grid grid-cols-3 gap-8">

            <div></div>

            <div
              className="text-center w-16 bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 rounded"
              onPointerDown={() => toggleUp(true)}
              onPointerUp={() => toggleUp(false)}
              onPointerLeave={() => toggleUp(false)}
              onPointerOut={() => toggleUp(false)}>
              up
            </div>

            <div></div>

            <div
              className="text-center w-16 bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 rounded"
              onPointerDown={() => toggleLeft(true)}
              onPointerUp={() => toggleLeft(false)}
              onPointerLeave={() => toggleLeft(false)}
              onPointerOut={() => toggleLeft(false)}>
              left
            </div>

            <div></div>

            <div
              className="text-center w-16 bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 rounded"
              onPointerDown={() => toggleRight(true)}
              onPointerUp={() => toggleRight(false)}
              onPointerLeave={() => toggleRight(false)}
              onPointerOut={() => toggleRight(false)}>
              right
            </div>

            <div></div>

            <div
              className="text-center w-16 bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 rounded"
              onPointerDown={() => toggleDown(true)}
              onPointerUp={() => toggleDown(false)}
              onPointerLeave={() => toggleDown(false)}
              onPointerOut={() => toggleDown(false)}>
              down
            </div>

            <div></div>

          </div>

        </div>

      </Html>

      <mesh
        {...props}
        ref={meshRef}
        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 ? 'red' : 'blue'} />
      </mesh>

    </>
  )

}
// Box End ///////////////////////////////////////////////////////////////////////////////

// Plane Start ///////////////////////////////////////////////////////////////////////////
const Plane = (props) => {
  const meshRef = useRef();
  const [hovered, setHover] = useState(false)
  const [active, setActive] = useState(false)

  const [up, toggleUp] = useState(false)
  const [left, toggleLeft] = useState(false)
  const [right, toggleRight] = useState(false)
  const [down, toggleDown] = useState(false)

  useFrame((state) => {
    meshRef.current.rotation.x += up ? -0.1 : 0
    meshRef.current.rotation.x += down ? 0.1 : 0
    meshRef.current.rotation.y += left ? -0.1 : 0
    meshRef.current.rotation.y += right ? 0.1 : 0
  }, [])
  
  return (
    <>

      <Html fullscreen>

        <h3 className="text-center text-2xl font-medium text-red-500">Hello World! This is 3D Plane Objec!!</h3>


        <div class="flex justify-center">

          <div
            className="text-center w-16 bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 rounded"
            onPointerDown={() => toggleUp(true)}
            onPointerUp={() => toggleUp(false)}
            onPointerLeave={() => toggleUp(false)}
            onPointerOut={() => toggleUp(false)}>
            up
          </div>

        </div>


        <div class="flex justify-center items-center gap-32">

          <div
            className="text-center w-16 bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 rounded"
            onPointerDown={() => toggleLeft(true)}
            onPointerUp={() => toggleLeft(false)}
            onPointerLeave={() => toggleLeft(false)}
            onPointerOut={() => toggleLeft(false)}>
            left
          </div>

          <div
            className="text-center w-16 bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 rounded"
            onPointerDown={() => toggleRight(true)}
            onPointerUp={() => toggleRight(false)}
            onPointerLeave={() => toggleRight(false)}
            onPointerOut={() => toggleRight(false)}>
            right
          </div>

        </div>


        <div class="flex justify-center items-center">

          <div
                className="w-16 bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 rounded text-center"
                onPointerDown={() => toggleDown(true)}
                onPointerUp={() => toggleDown(false)}
                onPointerLeave={() => toggleDown(false)}
                onPointerOut={() => toggleDown(false)}>
                down
          </div>

        </div>

      </Html>

      <mesh
        {...props}
        ref={meshRef}
        scale={active ? 1.5 : 1}
        onClick={(event) => setActive(!active)}
        onPointerOver={(event) => setHover(true)}
        onPointerOut={(event) => setHover(false)}>
        <planeGeometry args={[2, 2, 2]} />
        <meshStandardMaterial color={hovered ? 'red' : 'blue'} side={DoubleSide} />
      </mesh>

    </>
  )

}
// Plane End /////////////////////////////////////////////////////////////////////////////

// Sphere Start //////////////////////////////////////////////////////////////////////////
const Sphere = (props) => {
  const meshRef = useRef();
  const [hovered, setHover] = useState(false)
  const [active, setActive] = useState(false)

  const [up, toggleUp] = useState(false)
  const [left, toggleLeft] = useState(false)
  const [right, toggleRight] = useState(false)
  const [down, toggleDown] = useState(false)

  useFrame((state) => {
    meshRef.current.rotation.x += up ? -0.1 : 0
    meshRef.current.rotation.x += down ? 0.1 : 0
    meshRef.current.rotation.y += left ? -0.1 : 0
    meshRef.current.rotation.y += right ? 0.1 : 0
  }, [])

  return (
    <>

      <Html fullscreen>

        <h3 className="text-center text-2xl font-medium text-black-500">Hello World! This is 3D Sphere Objec!!</h3>


        <div className="relative h-screen w-screen bg-opacity-50 bg-green-500">

          <div
            className="absolute top-2 right-1/2 text-center w-16 bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 rounded"
            onPointerDown={() => toggleUp(true)}
            onPointerUp={() => toggleUp(false)}
            onPointerLeave={() => toggleUp(false)}
            onPointerOut={() => toggleUp(false)}>
            up
          </div>


          <div
            className="absolute top-44 left-1/3 text-center w-16 bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 rounded"
            onPointerDown={() => toggleLeft(true)}
            onPointerUp={() => toggleLeft(false)}
            onPointerLeave={() => toggleLeft(false)}
            onPointerOut={() => toggleLeft(false)}>
            left
          </div>


          <div
            className="absolute top-44 right-1/3 text-center w-16 bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 rounded"
            onPointerDown={() => toggleRight(true)}
            onPointerUp={() => toggleRight(false)}
            onPointerLeave={() => toggleRight(false)}
            onPointerOut={() => toggleRight(false)}>
            right
          </div>


          <div
            className="absolute bottom-10 right-1/2 text-center w-16 bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 rounded"
            onPointerDown={() => toggleDown(true)}
            onPointerUp={() => toggleDown(false)}
            onPointerLeave={() => toggleDown(false)}
            onPointerOut={() => toggleDown(false)}>
            down
          </div>

        </div>

      </Html>

      <mesh
        {...props}
        ref={meshRef}
        scale={active ? 1.5 : 1}
        onClick={(event) => setActive(!active)}
        onPointerOver={(event) => setHover(true)}
        onPointerOut={(event) => setHover(false)}>
        <sphereGeometry args={[2, 25, 25]} />
        <meshStandardMaterial wireframe color={hovered ? 'red' : 'blue'} />
      </mesh>

    </>
  )

}
// Sphere End ////////////////////////////////////////////////////////////////////////////

ReactDOM.render(
  <BrowserRouter>
    <App />
  </BrowserRouter>,
  document.getElementById("root")
);


React Three Fiber 8.16.1, React Router 6.22.3 & Tailwind CSS 3.4.3 Test05

React Three Fiber 8.16.1, React Router 6.22.3 & Tailwind CSS 3.4.3 Test05

「Box」クリック時
「Plane」クリック時
「Sphere」クリック時

テクスチャを貼った「Box」「Plane」「Sphere」を表示してみました。

HTML

<div id="root"></div>

CSS なし

JS(JavaScript)

import React, { useRef, useState } from 'https://esm.sh/react@18.2.0'
import ReactDOM from "https://esm.sh/react-dom@18.2.0";
import {
  BrowserRouter,
  NavLink,
  Routes,
  Route
} from "https://esm.sh/react-router-dom@6.22.3";

import { Canvas, useFrame } from 'https://esm.sh/@react-three/fiber@8.16.1'
import { Html } from 'https://esm.sh/@react-three/drei@9.105.4'
import { DoubleSide } from 'https://esm.sh/three@0.163.0'
import * as THREE from 'https://esm.sh/three@0.163.0'

const App = () => {

  const activeLink = "bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded";

  return (
    <>

      <nav className="container flex justify-around py-8 mx-auto bg-white">

        <div>
          <h3 className="text-2xl font-medium text-blue-500">LOGO</h3>
        </div>
      
        <div className="space-x-8">
          <NavLink
            to="/"
            className={({ isActive }) =>
              isActive ? activeLink : ""
            }
          >
            Box
          </NavLink>

          <NavLink
            to="/plane"
            className={({ isActive }) =>
              isActive ? activeLink : ""
            }
          >
            Plane
          </NavLink>

          <NavLink
            to="/sphere"
            className={({ isActive }) =>
              isActive ? activeLink : ""
            }
          >
            Sphere
          </NavLink>
        </div>
      </nav>


      <div className="w-full h-screen bg-[#303030]">

        <Canvas>

          <ambientLight intensity={Math.PI / 2} />
          <spotLight position={[10, 10, 10]} angle={0.15} penumbra={1} decay={0} intensity={Math.PI} />
          <pointLight position={[-10, -10, -10]} decay={0} intensity={Math.PI} />

          <Routes>

            <Route path="/" element={<Box position={[0, 0, 2.5]} />}></Route>
            <Route path="/plane" element={<Plane position={[0, 0, 0]} />}></Route>
            <Route path="/sphere" element={<Sphere position={[0, 0, 0]} />}></Route>

          </Routes>

        </Canvas>

      </div>


    </>
  );
}

// Box Start /////////////////////////////////////////////////////////////////////////////
const Box = (props) => {

  const textureImageURL = 'https://rawcdn.githack.com/mrdoob/three.js/2ff77e4b335e31c108aac839a07401664998c730/examples/textures/crate.gif'
  const textureImg = new THREE.TextureLoader().load(textureImageURL);

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

  const [up, toggleUp] = useState(false)
  const [left, toggleLeft] = useState(false)
  const [right, toggleRight] = useState(false)
  const [down, toggleDown] = useState(false)

  useFrame((state) => {
    meshRef.current.rotation.x += up ? -0.1 : 0
    meshRef.current.rotation.x += down ? 0.1 : 0
    meshRef.current.rotation.y += left ? -0.1 : 0
    meshRef.current.rotation.y += right ? 0.1 : 0
  }, [])

  return (
    <>

      <Html fullscreen>

        <h3 className="text-center text-2xl font-medium text-blue-500">Hello World! This is 3D Cube Objec!!</h3>

        <div class="flex justify-center">

          <div class="grid grid-cols-3 gap-8">

            <div></div>

            <div
              className="text-center w-16 bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 rounded"
              onPointerDown={() => toggleUp(true)}
              onPointerUp={() => toggleUp(false)}
              onPointerLeave={() => toggleUp(false)}
              onPointerOut={() => toggleUp(false)}>
              up
            </div>

            <div></div>

            <div
              className="text-center w-16 bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 rounded"
              onPointerDown={() => toggleLeft(true)}
              onPointerUp={() => toggleLeft(false)}
              onPointerLeave={() => toggleLeft(false)}
              onPointerOut={() => toggleLeft(false)}>
              left
            </div>

            <div></div>

            <div
              className="text-center w-16 bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 rounded"
              onPointerDown={() => toggleRight(true)}
              onPointerUp={() => toggleRight(false)}
              onPointerLeave={() => toggleRight(false)}
              onPointerOut={() => toggleRight(false)}>
              right
            </div>

            <div></div>

            <div
              className="text-center w-16 bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 rounded"
              onPointerDown={() => toggleDown(true)}
              onPointerUp={() => toggleDown(false)}
              onPointerLeave={() => toggleDown(false)}
              onPointerOut={() => toggleDown(false)}>
              down
            </div>

            <div></div>

          </div>

        </div>

      </Html>

      <mesh

        {...props}
        ref={meshRef}
        scale={active ? 1.5 : 1}
        onClick={(event) => setActive(!active)}>
        <boxGeometry args={[1, 1, 1]} />
        <meshStandardMaterial map={textureImg} />
      </mesh>

    </>
  )

}
// Box End ///////////////////////////////////////////////////////////////////////////////

// Plane Start ///////////////////////////////////////////////////////////////////////////
const Plane = (props) => {

  const textureImageURL = slimeLikeImage
  const textureImg = new THREE.TextureLoader().load(textureImageURL);

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

  const [up, toggleUp] = useState(false)
  const [left, toggleLeft] = useState(false)
  const [right, toggleRight] = useState(false)
  const [down, toggleDown] = useState(false)

  useFrame((state) => {
    meshRef.current.rotation.x += up ? -0.1 : 0
    meshRef.current.rotation.x += down ? 0.1 : 0
    meshRef.current.rotation.y += left ? -0.1 : 0
    meshRef.current.rotation.y += right ? 0.1 : 0
  }, [])
  
  return (
    <>

      <Html fullscreen>

        <h3 className="text-center text-2xl font-medium text-red-500">Hello World! This is 3D Plane Objec!!</h3>


        <div class="flex justify-center">

          <div
            className="text-center w-16 bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 rounded"
            onPointerDown={() => toggleUp(true)}
            onPointerUp={() => toggleUp(false)}
            onPointerLeave={() => toggleUp(false)}
            onPointerOut={() => toggleUp(false)}>
            up
          </div>

        </div>


        <div class="flex justify-center items-center gap-32">

          <div
            className="text-center w-16 bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 rounded"
            onPointerDown={() => toggleLeft(true)}
            onPointerUp={() => toggleLeft(false)}
            onPointerLeave={() => toggleLeft(false)}
            onPointerOut={() => toggleLeft(false)}>
            left
          </div>

          <div
            className="text-center w-16 bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 rounded"
            onPointerDown={() => toggleRight(true)}
            onPointerUp={() => toggleRight(false)}
            onPointerLeave={() => toggleRight(false)}
            onPointerOut={() => toggleRight(false)}>
            right
          </div>

        </div>


        <div class="flex justify-center items-center">

          <div
                className="w-16 bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 rounded text-center"
                onPointerDown={() => toggleDown(true)}
                onPointerUp={() => toggleDown(false)}
                onPointerLeave={() => toggleDown(false)}
                onPointerOut={() => toggleDown(false)}>
                down
          </div>

        </div>

      </Html>

      <mesh
        {...props}
        ref={meshRef}
        scale={active ? 1.5 : 1}
        onClick={(event) => setActive(!active)}>
        <planeGeometry args={[2, 2, 2]} />
        <meshStandardMaterial  map={textureImg}  side={DoubleSide} />
      </mesh>

    </>
  )

}
// Plane End /////////////////////////////////////////////////////////////////////////////

// Sphere Start //////////////////////////////////////////////////////////////////////////
const Sphere = (props) => {

  const textureImageURL = earthImage
  const textureImg = new THREE.TextureLoader().load(textureImageURL);

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

  const [up, toggleUp] = useState(false)
  const [left, toggleLeft] = useState(false)
  const [right, toggleRight] = useState(false)
  const [down, toggleDown] = useState(false)

  useFrame((state) => {
    meshRef.current.rotation.x += up ? -0.1 : 0
    meshRef.current.rotation.x += down ? 0.1 : 0
    meshRef.current.rotation.y += left ? -0.1 : 0
    meshRef.current.rotation.y += right ? 0.1 : 0
  }, [])

  return (
    <>

      <Html fullscreen>

        <h3 className="text-center text-2xl font-medium text-green-500">Hello World! This is 3D Sphere Objec!!</h3>


        <div className="relative h-screen w-screen bg-opacity-0 bg-green-500">

          <div
            className="absolute top-2 m-auto left-0 right-0 text-center w-16 bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 rounded select-none"
            onPointerDown={() => toggleUp(true)}
            onPointerUp={() => toggleUp(false)}
            onPointerLeave={() => toggleUp(false)}
            onPointerOut={() => toggleUp(false)}>
            up
          </div>


          <div
            className="absolute bottom-44 left-1/3 text-center w-16 bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 rounded select-none"
            onPointerDown={() => toggleLeft(true)}
            onPointerUp={() => toggleLeft(false)}
            onPointerLeave={() => toggleLeft(false)}
            onPointerOut={() => toggleLeft(false)}>
            left
          </div>


          <div
            className="absolute bottom-44 right-1/3 text-center w-16 bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 rounded select-none"
            onPointerDown={() => toggleRight(true)}
            onPointerUp={() => toggleRight(false)}
            onPointerLeave={() => toggleRight(false)}
            onPointerOut={() => toggleRight(false)}>
            right
          </div>


          <div
            className="absolute bottom-36 m-auto left-0 right-0 text-center w-16 bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 rounded select-none"
            onPointerDown={() => toggleDown(true)}
            onPointerUp={() => toggleDown(false)}
            onPointerLeave={() => toggleDown(false)}
            onPointerOut={() => toggleDown(false)}>
            down
          </div>

        </div>

      </Html>

      <mesh
        {...props}
        ref={meshRef}
        scale={active ? 1.5 : 1}
        onClick={(event) => setActive(!active)}>
        <sphereGeometry args={[2, 25, 25]} />
        <meshStandardMaterial map={textureImg} />
      </mesh>

    </>
  )

}
// Sphere End ////////////////////////////////////////////////////////////////////////////

ReactDOM.render(
  <BrowserRouter>
    <App />
  </BrowserRouter>,
  document.getElementById("root")
);


React Three Fiber 8.16.1, React Router 6.22.3 & Tailwind CSS 3.4.3 Test06

React Three Fiber 8.16.1, React Router 6.22.3 & Tailwind CSS 3.4.3 Test06(失敗)

「Box」クリック時
「Plane」クリック時
「Sphere」クリック時

Test06ではglTF形式の3Dモデルを表示してみました。 が、useRef()を使用していないせいかReact Routersによる画面遷移時に3Dモデルがうまく表示(保持?)されず。。。 「Box」「Plane」「Sphere」のうち、最後にクリックされた画面の3Dオブジェクトだけが再クリック時に再表示される状態となってしまっています。 


React Three Fiber 8.16.1, React Router 6.22.3 & Tailwind CSS 3.4.3 Test06_02(失敗)

次に「Box」からの画面にのみuseRef()を使用して、比較のための差分的・部分的な修正をしたものがTest06_02になります。 部分的な修正のため、useRef()を使用している「Box」画面の3Dモデルのみ安定的に再表示され、「Plane」「Sphere」の画面ではひきつづき最後に表示された画面の3Dモデルだけが再表示される状態となっています。 そのようにして、よく理解していないuseRef()使用時の挙動の比較・確認なんかもしていいます。


React Three Fiber 8.16.1, React Router 6.22.3 & Tailwind CSS 3.4.3 Test06_03

最後に修正を完了したTest06_03です。 クリックによる画面遷移時にすべての画面の3Dモデルが再表示されるようにしています。 機能が完成したので画面遷移用リンクの「Box」「Plane」「Sphere」表示も、機能に合わせて「Model01」「Model02」「Model03」表示に修正しました。

「Model01」クリック時
「Model02」クリック時
「Model03」クリック時

HTML

<div id="root"></div>

CSS なし

JS(JavaScript)

import React, { useRef, useState, Suspense } from 'https://esm.sh/react@18.2.0'
import ReactDOM from "https://esm.sh/react-dom@18.2.0";
import {
  BrowserRouter,
  NavLink,
  Routes,
  Route
} from "https://esm.sh/react-router-dom@6.22.3";

import { Canvas, useFrame } from 'https://esm.sh/@react-three/fiber@8.16.1'
import { Html, useProgress, useGLTF } from 'https://esm.sh/@react-three/drei@9.105.4'

const App = () => {

  const activeLink = "bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded";

  return (
    <>

      <nav className="container flex justify-around py-8 mx-auto bg-white">

        <div>
          <h3 className="text-2xl font-medium text-blue-500">LOGO</h3>
        </div>
      
        <div className="space-x-8">
          <NavLink
            to="/"
            className={({ isActive }) =>
              isActive ? activeLink : ""
            }
          >
            Model01
          </NavLink>

          <NavLink
            to="/model02"
            className={({ isActive }) =>
              isActive ? activeLink : ""
            }
          >
            Model02
          </NavLink>

          <NavLink
            to="/model03"
            className={({ isActive }) =>
              isActive ? activeLink : ""
            }
          >
            Model03
          </NavLink>
        </div>
      </nav>


      <div className="w-full h-screen bg-[#333333]">

        <Canvas>

          <ambientLight intensity={Math.PI / 2} />
          <spotLight position={[10, 10, 10]} angle={0.15} penumbra={1} decay={0} intensity={Math.PI} />
          <pointLight position={[-10, -10, -10]} decay={0} intensity={Math.PI} />

          <Routes>

            <Route path="/" element={<Suspense fallback={<Loader />}><Model01 position={[0, 0.3, 0]} scale={0.009} /></Suspense>}></Route>
            <Route path="/model02" element={<Suspense fallback={<Loader />}><Model02 position={[0, -1.5, 0]} scale={70} /></Suspense>}></Route>
            <Route path="/model03" element={<Suspense fallback={<Loader />}><Model03 position={[0, -1, 0]} scale={1} /></Suspense>}></Route>

          </Routes>

        </Canvas>

      </div>


    </>
  );
}

const Loader = () => {
  const { progress } = useProgress()
  return (
    <Html center>
      {progress} % loaded
    </Html>
  )
}

// Model01 Start /////////////////////////////////////////////////////////////////////////////
const Model01 = (props) => {

  const group = useRef()

  const gltf = useGLTF('https://raw.githack.com/KhronosGroup/glTF-Sample-Models/master/2.0/2CylinderEngine/glTF-Binary/2CylinderEngine.glb')

  const [up, toggleUp] = useState(false)
  const [left, toggleLeft] = useState(false)
  const [right, toggleRight] = useState(false)
  const [down, toggleDown] = useState(false)

  useFrame((state) => {
    gltf.scene.rotation.x += up ? -0.1 : 0
    gltf.scene.rotation.x += down ? 0.1 : 0
    gltf.scene.rotation.y += left ? -0.1 : 0
    gltf.scene.rotation.y += right ? 0.1 : 0
  }, [])

  return (
    <>

      <Html fullscreen>

        <h3 className="text-center text-2xl font-medium text-white">Hello World!</h3>

        <div class="flex justify-center">

          <div class="grid grid-cols-3 gap-8">

            <div></div>

            <div
              className="text-center w-16 bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 rounded select-none"
              onPointerDown={() => toggleUp(true)}
              onPointerUp={() => toggleUp(false)}
              onPointerLeave={() => toggleUp(false)}
              onPointerOut={() => toggleUp(false)}>
              up
            </div>

            <div></div>

            <div
              className="text-center w-16 bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 rounded select-none"
              onPointerDown={() => toggleLeft(true)}
              onPointerUp={() => toggleLeft(false)}
              onPointerLeave={() => toggleLeft(false)}
              onPointerOut={() => toggleLeft(false)}>
              left
            </div>

            <div></div>

            <div
              className="text-center w-16 bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 rounded select-none"
              onPointerDown={() => toggleRight(true)}
              onPointerUp={() => toggleRight(false)}
              onPointerLeave={() => toggleRight(false)}
              onPointerOut={() => toggleRight(false)}>
              right
            </div>

            <div></div>

            <div
              className="text-center w-16 bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 rounded select-none"
              onPointerDown={() => toggleDown(true)}
              onPointerUp={() => toggleDown(false)}
              onPointerLeave={() => toggleDown(false)}
              onPointerOut={() => toggleDown(false)}>
              down
            </div>

            <div></div>

          </div>

        </div>

      </Html>


      <group ref={group} dispose={null}>
        <primitive object={gltf.scene} position={props.position} scale={props.scale} />
      </group>


    </>
  )

}
// Model01 End ///////////////////////////////////////////////////////////////////////////////

// Model02 Start ///////////////////////////////////////////////////////////////////////////
const Model02 = (props) => {

  const group = useRef()

  const gltf = useGLTF('https://raw.githack.com/KhronosGroup/glTF-Sample-Models/master/2.0/Avocado/glTF-Binary/Avocado.glb')

  const [up, toggleUp] = useState(false)
  const [left, toggleLeft] = useState(false)
  const [right, toggleRight] = useState(false)
  const [down, toggleDown] = useState(false)

  useFrame((state) => {
    gltf.scene.rotation.x += up ? -0.1 : 0
    gltf.scene.rotation.x += down ? 0.1 : 0
    gltf.scene.rotation.y += left ? -0.1 : 0
    gltf.scene.rotation.y += right ? 0.1 : 0
  }, [])

  return (
    <>

      <Html fullscreen>

        <h3 className="text-center text-2xl font-medium text-green-500">Hello World!</h3>


        <div class="flex justify-center">

          <div
            className="text-center w-16 bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 rounded"
            onPointerDown={() => toggleUp(true)}
            onPointerUp={() => toggleUp(false)}
            onPointerLeave={() => toggleUp(false)}
            onPointerOut={() => toggleUp(false)}>
            up
          </div>

        </div>


        <div class="flex justify-center items-center gap-32">

          <div
            className="text-center w-16 bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 rounded"
            onPointerDown={() => toggleLeft(true)}
            onPointerUp={() => toggleLeft(false)}
            onPointerLeave={() => toggleLeft(false)}
            onPointerOut={() => toggleLeft(false)}>
            left
          </div>

          <div
            className="text-center w-16 bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 rounded"
            onPointerDown={() => toggleRight(true)}
            onPointerUp={() => toggleRight(false)}
            onPointerLeave={() => toggleRight(false)}
            onPointerOut={() => toggleRight(false)}>
            right
          </div>

        </div>


        <div class="flex justify-center items-center">

          <div
                className="w-16 bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 rounded text-center"
                onPointerDown={() => toggleDown(true)}
                onPointerUp={() => toggleDown(false)}
                onPointerLeave={() => toggleDown(false)}
                onPointerOut={() => toggleDown(false)}>
                down
          </div>

        </div>

      </Html>


      <group ref={group} dispose={null}>
        <primitive object={gltf.scene} position={props.position} scale={props.scale} />
      </group>


    </>
  )

}
// Model02 End /////////////////////////////////////////////////////////////////////////////

// Model03 Start //////////////////////////////////////////////////////////////////////////
const Model03 = (props) => {

  const group = useRef()

  const gltf = useGLTF('https://rawcdn.githack.com/siouxcitizen/3DModel/a1c2e47550ca20de421f6d779229f66efab07830/yuusha.gltf')

  const [up, toggleUp] = useState(false)
  const [left, toggleLeft] = useState(false)
  const [right, toggleRight] = useState(false)
  const [down, toggleDown] = useState(false)

  useFrame((state) => {
    gltf.scene.rotation.x += up ? -0.1 : 0
    gltf.scene.rotation.x += down ? 0.1 : 0
    gltf.scene.rotation.y += left ? -0.1 : 0
    gltf.scene.rotation.y += right ? 0.1 : 0
  }, [])

  return (
    <>

      <Html fullscreen>

        <h3 className="text-center text-2xl font-medium text-blue-500">Hello World!</h3>


        <div className="relative h-screen w-screen bg-opacity-0 bg-green-500">

          <div
            className="absolute top-2 m-auto left-0 right-0 text-center w-16 bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 rounded select-none"
            onPointerDown={() => toggleUp(true)}
            onPointerUp={() => toggleUp(false)}
            onPointerLeave={() => toggleUp(false)}
            onPointerOut={() => toggleUp(false)}>
            up
          </div>


          <div
            className="absolute bottom-44 left-1/3 text-center w-16 bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 rounded select-none"
            onPointerDown={() => toggleLeft(true)}
            onPointerUp={() => toggleLeft(false)}
            onPointerLeave={() => toggleLeft(false)}
            onPointerOut={() => toggleLeft(false)}>
            left
          </div>


          <div
            className="absolute bottom-44 right-1/3 text-center w-16 bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 rounded select-none"
            onPointerDown={() => toggleRight(true)}
            onPointerUp={() => toggleRight(false)}
            onPointerLeave={() => toggleRight(false)}
            onPointerOut={() => toggleRight(false)}>
            right
          </div>


          <div
            className="absolute bottom-36 m-auto left-0 right-0 text-center w-16 bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 rounded select-none"
            onPointerDown={() => toggleDown(true)}
            onPointerUp={() => toggleDown(false)}
            onPointerLeave={() => toggleDown(false)}
            onPointerOut={() => toggleDown(false)}>
            down
          </div>

        </div>

      </Html>


      <group ref={group} dispose={null}>
        <primitive object={gltf.scene} position={props.position} scale={props.scale} />
      </group>


    </>
  )

}
// Model03 End ////////////////////////////////////////////////////////////////////////////

ReactDOM.render(
  <BrowserRouter>
    <App />
  </BrowserRouter>,
  document.getElementById("root")
);


React Three Fiber 8.16.1, React Router 6.22.3 & Tailwind CSS 3.4.3 Test07

React Three Fiber 8.16.1, React Router 6.22.3 & Tailwind CSS 3.4.3 Test07

「Parrot」クリック時
「Flamingo」クリック時
「Stork」クリック時

glTF形式の3Dモデルのアニメーションを表示させています。

HTML

<div id="root"></div>

CSS なし

JS(JavaScript)

import React, { useRef, useState, useEffect, Suspense } from 'https://esm.sh/react@18.2.0'
import ReactDOM from "https://esm.sh/react-dom@18.2.0";
import {
  BrowserRouter,
  NavLink,
  Routes,
  Route
} from "https://esm.sh/react-router-dom@6.22.3";

import { Canvas, useFrame } from 'https://esm.sh/@react-three/fiber@8.16.1'
import { Html, useProgress, useGLTF, useAnimations } from 'https://esm.sh/@react-three/drei@9.105.4'

const ModelPath01 = 'https://cdn.jsdelivr.net/gh/mrdoob/three.js@r110/examples/models/gltf/Parrot.glb'
const ModelPath02 = 'https://cdn.jsdelivr.net/gh/mrdoob/three.js@r110/examples/models/gltf/Flamingo.glb'
const ModelPath03 = 'https://cdn.jsdelivr.net/gh/mrdoob/three.js@r110/examples/models/gltf/Stork.glb'

const App = () => {

  const activeLink = "bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded";

  return (
    <>

      <nav className="container flex justify-around py-8 mx-auto bg-white">

        <div>
          <h3 className="text-2xl font-medium text-blue-500">LOGO</h3>
        </div>
      
        <div className="space-x-8">
          <NavLink
            to="/"
            className={({ isActive }) =>
              isActive ? activeLink : ""
            }
          >
            Parrot
          </NavLink>

          <NavLink
            to="/flamingo"
            className={({ isActive }) =>
              isActive ? activeLink : ""
            }
          >
            Flamingo
          </NavLink>

          <NavLink
            to="/stork"
            className={({ isActive }) =>
              isActive ? activeLink : ""
            }
          >
            Stork
          </NavLink>
        </div>
      </nav>


      <div className="w-full h-screen bg-[#333333]">

        <Canvas>

          <ambientLight intensity={Math.PI / 2} />
          <spotLight position={[10, 10, 10]} angle={0.15} penumbra={1} decay={0} intensity={Math.PI} />
          <pointLight position={[-10, -10, -10]} decay={0} intensity={Math.PI} />

          <Routes>

            <Route path="/" element={<Suspense fallback={<Loader />}><Model01 position={[0, -1.5, 0]} rotation={[0, 0, 0]} scale={0.3} /></Suspense>}></Route>
            <Route path="/flamingo" element={<Suspense fallback={<Loader />}><Model02 position={[0, -1, 0]} rotation={[0, 0, 0]} scale={0.2} /></Suspense>}></Route>
            <Route path="/stork" element={<Suspense fallback={<Loader />}><Model03 position={[0, 0, 0]} rotation={[0, 0, 0]} scale={0.20} /></Suspense>}></Route>

          </Routes>

        </Canvas>

      </div>


    </>
  );
}

const Loader = () => {
  const { progress } = useProgress()
  return (
    <Html center>
      {progress} % loaded
    </Html>
  )
}

// Model01 Start /////////////////////////////////////////////////////////////////////////////
const Model01 = (props) => {

  const group = useRef()

  const gltf = useGLTF(ModelPath01)

  const { actions } = useAnimations(gltf.animations, group)

  useEffect(() => {
    //console.log(actions) // find out the name of your action
    actions['parrot_A_'].play()
  })

  const [up, toggleUp] = useState(false)
  const [left, toggleLeft] = useState(false)
  const [right, toggleRight] = useState(false)
  const [down, toggleDown] = useState(false)

  useFrame((state) => {
    gltf.scene.rotation.x += up ? -0.1 : 0
    gltf.scene.rotation.x += down ? 0.1 : 0
    gltf.scene.rotation.y += left ? -0.1 : 0
    gltf.scene.rotation.y += right ? 0.1 : 0
  }, [])

  return (
    <>

      <Html fullscreen>

        <h3 className="text-center text-2xl font-medium text-blue-500">Hello World! This is Parrot!</h3>

        <div class="flex justify-center">

          <div class="grid grid-cols-3 gap-8">

            <div></div>

            <div
              className="text-center w-16 bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 rounded select-none"
              onPointerDown={() => toggleUp(true)}
              onPointerUp={() => toggleUp(false)}
              onPointerLeave={() => toggleUp(false)}
              onPointerOut={() => toggleUp(false)}>
              up
            </div>

            <div></div>

            <div
              className="text-center w-16 bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 rounded select-none"
              onPointerDown={() => toggleLeft(true)}
              onPointerUp={() => toggleLeft(false)}
              onPointerLeave={() => toggleLeft(false)}
              onPointerOut={() => toggleLeft(false)}>
              left
            </div>

            <div></div>

            <div
              className="text-center w-16 bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 rounded select-none"
              onPointerDown={() => toggleRight(true)}
              onPointerUp={() => toggleRight(false)}
              onPointerLeave={() => toggleRight(false)}
              onPointerOut={() => toggleRight(false)}>
              right
            </div>

            <div></div>

            <div
              className="text-center w-16 bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 rounded select-none"
              onPointerDown={() => toggleDown(true)}
              onPointerUp={() => toggleDown(false)}
              onPointerLeave={() => toggleDown(false)}
              onPointerOut={() => toggleDown(false)}>
              down
            </div>

            <div></div>

          </div>

        </div>

      </Html>


      <group ref={group} dispose={null}>
        <group scale={props.scale}>
          <primitive object={gltf.scene} position={props.position} rotation={props.rotation} scale={props.scale} />
        </group>
      </group>


    </>
  )

}
// Model01 End ///////////////////////////////////////////////////////////////////////////////

// Model02 Start ///////////////////////////////////////////////////////////////////////////
const Model02 = (props) => {

  const group = useRef()

  const gltf = useGLTF(ModelPath02)

  const { actions } = useAnimations(gltf.animations, group)

  useEffect(() => {
    //console.log(actions) // find out the name of your action
    actions['flamingo_flyA_'].play()
  })

  const [up, toggleUp] = useState(false)
  const [left, toggleLeft] = useState(false)
  const [right, toggleRight] = useState(false)
  const [down, toggleDown] = useState(false)

  useFrame((state) => {
    gltf.scene.rotation.x += up ? -0.1 : 0
    gltf.scene.rotation.x += down ? 0.1 : 0
    gltf.scene.rotation.y += left ? -0.1 : 0
    gltf.scene.rotation.y += right ? 0.1 : 0
  }, [])

  return (
    <>

      <Html fullscreen>

        <h3 className="text-center text-2xl font-medium text-blue-500">Hello World! This is Flamingo!</h3>


        <div class="flex justify-center">

          <div
            className="text-center w-16 bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 rounded select-none"
            onPointerDown={() => toggleUp(true)}
            onPointerUp={() => toggleUp(false)}
            onPointerLeave={() => toggleUp(false)}
            onPointerOut={() => toggleUp(false)}>
            up
          </div>

        </div>


        <div class="flex justify-center items-center gap-32">

          <div
            className="text-center w-16 bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 rounded select-none"
            onPointerDown={() => toggleLeft(true)}
            onPointerUp={() => toggleLeft(false)}
            onPointerLeave={() => toggleLeft(false)}
            onPointerOut={() => toggleLeft(false)}>
            left
          </div>

          <div
            className="text-center w-16 bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 rounded select-none"
            onPointerDown={() => toggleRight(true)}
            onPointerUp={() => toggleRight(false)}
            onPointerLeave={() => toggleRight(false)}
            onPointerOut={() => toggleRight(false)}>
            right
          </div>

        </div>


        <div class="flex justify-center items-center">

          <div
                className="text-center w-16 bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 rounded select-none"
                onPointerDown={() => toggleDown(true)}
                onPointerUp={() => toggleDown(false)}
                onPointerLeave={() => toggleDown(false)}
                onPointerOut={() => toggleDown(false)}>
                down
          </div>

        </div>

      </Html>


      <group ref={group} dispose={null}>
        <group scale={props.scale}>
          <primitive object={gltf.scene} position={props.position} rotation={props.rotation} scale={props.scale} />
        </group>
      </group>


    </>
  )

}
// Model02 End /////////////////////////////////////////////////////////////////////////////

// Model03 Start //////////////////////////////////////////////////////////////////////////
const Model03 = (props) => {

  const group = useRef()

  const gltf = useGLTF(ModelPath03)

  const { actions } = useAnimations(gltf.animations, group)

  useEffect(() => {
    //console.log(actions) // find out the name of your action
    actions['storkFly_B_'].play()
  })

  const [up, toggleUp] = useState(false)
  const [left, toggleLeft] = useState(false)
  const [right, toggleRight] = useState(false)
  const [down, toggleDown] = useState(false)

  useFrame((state) => {
    gltf.scene.rotation.x += up ? -0.1 : 0
    gltf.scene.rotation.x += down ? 0.1 : 0
    gltf.scene.rotation.y += left ? -0.1 : 0
    gltf.scene.rotation.y += right ? 0.1 : 0
  }, [])

  return (
    <>

      <Html fullscreen>

        <h3 className="text-center text-2xl font-medium text-blue-500">Hello World! This is Stork!</h3>


        <div className="relative h-screen w-screen bg-opacity-0 bg-green-500">

          <div
            className="absolute top-2 m-auto left-0 right-0 text-center w-16 bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 rounded select-none"
            onPointerDown={() => toggleUp(true)}
            onPointerUp={() => toggleUp(false)}
            onPointerLeave={() => toggleUp(false)}
            onPointerOut={() => toggleUp(false)}>
            up
          </div>


          <div
            className="absolute bottom-44 left-1/3 text-center w-16 bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 rounded select-none"
            onPointerDown={() => toggleLeft(true)}
            onPointerUp={() => toggleLeft(false)}
            onPointerLeave={() => toggleLeft(false)}
            onPointerOut={() => toggleLeft(false)}>
            left
          </div>


          <div
            className="absolute bottom-44 right-1/3 text-center w-16 bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 rounded select-none"
            onPointerDown={() => toggleRight(true)}
            onPointerUp={() => toggleRight(false)}
            onPointerLeave={() => toggleRight(false)}
            onPointerOut={() => toggleRight(false)}>
            right
          </div>


          <div
            className="absolute bottom-36 m-auto left-0 right-0 text-center w-16 bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 rounded select-none"
            onPointerDown={() => toggleDown(true)}
            onPointerUp={() => toggleDown(false)}
            onPointerLeave={() => toggleDown(false)}
            onPointerOut={() => toggleDown(false)}>
            down
          </div>

        </div>

      </Html>


      <group ref={group} dispose={null}>
        <group scale={props.scale}>
          <primitive object={gltf.scene} position={props.position} rotation={props.rotation} scale={props.scale} />
        </group>
      </group>


    </>
  )

}
// Model03 End ////////////////////////////////////////////////////////////////////////////

ReactDOM.render(
  <BrowserRouter>
    <App />
  </BrowserRouter>,
  document.getElementById("root")
);


React Three Fiber 8.16.1, React Router 6.22.3 & Tailwind CSS 3.4.3 Test08

React Three Fiber 8.16.1, React Router 6.22.3 & Tailwind CSS 3.4.3 Test08

初期画面
「Fox」クリック時
「Soldier」クリック時
「Robot」クリック時

glTF形式の3Dモデルのアニメーションを切り替えることのできる画面を、「Fox」「Soldier」「Robot」クリックで選択表示させることができるコードです。 またボタン数が多くなってきたので3Dモデルが見やすいように、ボタン上にカーソルが来るか、ボタンが押されるかする以外はボタンの白枠と文字だけが表示されるようにしました。

HTML

<div id="root"></div>

CSS なし

JS(JavaScript)

import React, { useRef, useState, useEffect, Suspense } from 'https://esm.sh/react@18.2.0'
import ReactDOM from "https://esm.sh/react-dom@18.2.0";
import {
  BrowserRouter,
  NavLink,
  Routes,
  Route
} from "https://esm.sh/react-router-dom@6.22.3";

import { Canvas, useFrame } from 'https://esm.sh/@react-three/fiber@8.16.1'
import { Html, useProgress, useGLTF, useAnimations } from 'https://esm.sh/@react-three/drei@9.105.4'

const ModelPath01 = 'https://rawcdn.githack.com/KhronosGroup/glTF-Sample-Models/8e9a5a6ad1a2790e2333e3eb48a1ee39f9e0e31b/2.0/Fox/glTF-Binary/Fox.glb'
const ModelPath02 = 'https://cdn.jsdelivr.net/gh/mrdoob/three.js@r110/examples/models/gltf/Soldier.glb'
const ModelPath03 = 'https://rawcdn.githack.com/mrdoob/three.js/36f9f34752a985359e2556c68a52234436cefdfa/examples/models/gltf/RobotExpressive/RobotExpressive.glb'

const App = () => {

  const activeLink = "bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded";

  return (
    <>

      <nav className="container flex justify-around py-8 mx-auto bg-white">

        <div>
          <h3 className="text-2xl font-medium text-blue-500">LOGO</h3>
        </div>
      
        <div className="space-x-8">
          <NavLink
            to="/"
            className={({ isActive }) =>
              isActive ? activeLink : ""
            }
          >
            Fox
          </NavLink>

          <NavLink
            to="/soldier"
            className={({ isActive }) =>
              isActive ? activeLink : ""
            }
          >
            Soldier
          </NavLink>

          <NavLink
            to="/robot"
            className={({ isActive }) =>
              isActive ? activeLink : ""
            }
          >
            Robot
          </NavLink>
        </div>
      </nav>


      <div className="w-full h-screen bg-[#333333]">

        <Canvas>

          <ambientLight intensity={Math.PI / 2} />
          <spotLight position={[10, 10, 10]} angle={0.15} penumbra={1} decay={0} intensity={Math.PI} />
          <pointLight position={[-10, -10, -10]} decay={0} intensity={Math.PI} />

          <Routes>

            <Route path="/" element={<Suspense fallback={<Loader />}><Model01 position={[0, -7, 0]} rotation={[0, 0, 0]} scale={0.21} /></Suspense>}></Route>
            <Route path="/soldier" element={<Suspense fallback={<Loader />}><Model02 position={[0, -1.5, 0]} rotation={[0, Math.PI, 0]} scale={1.7} /></Suspense>}></Route>
            <Route path="/robot" element={<Suspense fallback={<Loader />}><Model03 position={[0, -1.5, 0]} rotation={[0, 0, 0]} scale={1} /></Suspense>}></Route>

          </Routes>

        </Canvas>

      </div>


    </>
  );
}

const Loader = () => {
  const { progress } = useProgress()
  return (
    <Html center>
      {progress} % loaded
    </Html>
  )
}

// Model01 Start /////////////////////////////////////////////////////////////////////////////
const Model01 = (props) => {

  const [action, setAction] = useState('Walk')
  const [previousAction, setPreviousAction] = useState('')

  const [up, toggleUp] = useState(false)
  const [left, toggleLeft] = useState(false)
  const [right, toggleRight] = useState(false)
  const [down, toggleDown] = useState(false)


  const group = useRef()

  const gltf = useGLTF(ModelPath01)

  const { actions } = useAnimations(gltf.animations, group)

  useEffect(() => {
    //console.log(actions) // find out the name of your action
    if (previousAction) {
      actions[previousAction].fadeOut(0.2)
      actions[previousAction].stop()
    }

    actions[action].play()
    actions[action].fadeIn(0.2)
  }, [action, actions])



  useFrame((state) => {
    gltf.scene.rotation.x += up ? -0.1 : 0
    gltf.scene.rotation.x += down ? 0.1 : 0
    gltf.scene.rotation.y += left ? -0.1 : 0
    gltf.scene.rotation.y += right ? 0.1 : 0
  }, [])

  return (
    <>

      <Html fullscreen>

        <h3 className="text-center text-2xl font-medium text-blue-500">Hello World! This is Fox!</h3>

        <div class="flex justify-center gap-4">
          <div className="text-center w-16 border border-white-500 hover:bg-blue-700 text-white font-bold py-2 rounded select-none"
            onClick={() => {
              setPreviousAction(action)
              setAction('Survey')
            }}>
            Survey
          </div>
          <div className="text-center w-16 border border-white-500 hover:bg-blue-700 text-white font-bold py-2 rounded select-none"
            onClick={() => {
              setPreviousAction(action)
              setAction('Walk')
            }}>
            Walk
          </div>
          <div className="text-center w-16 border border-white-500 hover:bg-blue-700 text-white font-bold py-2 rounded select-none"
            onClick={() => {
              setPreviousAction(action)
              setAction('Run')
            }}>
            Run
          </div>
        </div>

        <div class="flex justify-center">

          <div class="grid grid-cols-3 gap-8">

            <div></div>

            <div
              className="text-center w-16 border border-white-500 hover:bg-blue-700 text-white font-bold py-2 rounded select-none"
              onPointerDown={() => toggleUp(true)}
              onPointerUp={() => toggleUp(false)}
              onPointerLeave={() => toggleUp(false)}
              onPointerOut={() => toggleUp(false)}>
              up
            </div>

            <div></div>

            <div
              className="text-center w-16 border border-white-500 hover:bg-blue-700 text-white font-bold py-2 rounded select-none"
              onPointerDown={() => toggleLeft(true)}
              onPointerUp={() => toggleLeft(false)}
              onPointerLeave={() => toggleLeft(false)}
              onPointerOut={() => toggleLeft(false)}>
              left
            </div>

            <div></div>

            <div
              className="text-center w-16 border border-white-500 hover:bg-blue-700 text-white font-bold py-2 rounded select-none"
              onPointerDown={() => toggleRight(true)}
              onPointerUp={() => toggleRight(false)}
              onPointerLeave={() => toggleRight(false)}
              onPointerOut={() => toggleRight(false)}>
              right
            </div>

            <div></div>

            <div
              className="text-center w-16 border border-white-500 hover:bg-blue-700 text-white font-bold py-2 rounded select-none"
              onPointerDown={() => toggleDown(true)}
              onPointerUp={() => toggleDown(false)}
              onPointerLeave={() => toggleDown(false)}
              onPointerOut={() => toggleDown(false)}>
              down
            </div>

            <div></div>

          </div>

        </div>

      </Html>


      <group ref={group} dispose={null}>
        <group scale={props.scale}>
          <primitive object={gltf.scene} position={props.position} rotation={props.rotation} scale={props.scale} />
        </group>
      </group>


    </>
  )

}
// Model01 End ///////////////////////////////////////////////////////////////////////////////

// Model02 Start ///////////////////////////////////////////////////////////////////////////
const Model02 = (props) => {

  const [action, setAction] = useState('Walk')
  const [previousAction, setPreviousAction] = useState('')

  const [up, toggleUp] = useState(false)
  const [left, toggleLeft] = useState(false)
  const [right, toggleRight] = useState(false)
  const [down, toggleDown] = useState(false)


  const group = useRef()

  const gltf = useGLTF(ModelPath02)

  const { actions } = useAnimations(gltf.animations, group)

  useEffect(() => {
    //console.log(actions) // find out the name of your action
    if (previousAction) {
      actions[previousAction].fadeOut(0.2)
      actions[previousAction].stop()
    }

    actions[action].play()
    actions[action].fadeIn(0.2)
  }, [action, actions])



  useFrame((state) => {
    gltf.scene.rotation.x += up ? -0.1 : 0
    gltf.scene.rotation.x += down ? 0.1 : 0
    gltf.scene.rotation.y += left ? -0.1 : 0
    gltf.scene.rotation.y += right ? 0.1 : 0
  }, [])

  return (
    <>

      <Html fullscreen>

        <h3 className="text-center text-2xl font-medium text-blue-500">Hello World! This is Soldier!</h3>

        <div class="flex justify-center gap-4">
          <div className="text-center w-16 border border-white-500 hover:bg-blue-700 text-white font-bold py-2 rounded select-none"
            onClick={() => {
              setPreviousAction(action)
              setAction('Idle')
            }}>
            Idle
          </div>
          <div className="text-center w-16 border border-white-500 hover:bg-blue-700 text-white font-bold py-2 rounded select-none"
            onClick={() => {
              setPreviousAction(action)
              setAction('Run')
            }}>
            Run
          </div>
          <div className="text-center w-16 border border-white-500 hover:bg-blue-700 text-white font-bold py-2 rounded select-none"
            onClick={() => {
              setPreviousAction(action)
              setAction('TPose')
            }}>
            TPose
          </div>
          <div className="text-center w-16 border border-white-500 hover:bg-blue-700 text-white font-bold py-2 rounded select-none"
            onClick={() => {
              setPreviousAction(action)
              setAction('Walk')
            }}>
            Walk
          </div>
        </div>

        <div class="flex justify-center">

          <div
            className="text-center w-16 border border-white-500 hover:bg-blue-700 text-white font-bold py-2 rounded select-none"
            onPointerDown={() => toggleUp(true)}
            onPointerUp={() => toggleUp(false)}
            onPointerLeave={() => toggleUp(false)}
            onPointerOut={() => toggleUp(false)}>
            up
          </div>

        </div>


        <div class="flex justify-center items-center gap-32">

          <div
            className="text-center w-16 border border-white-500 hover:bg-blue-700 text-white font-bold py-2 rounded select-none"
            onPointerDown={() => toggleLeft(true)}
            onPointerUp={() => toggleLeft(false)}
            onPointerLeave={() => toggleLeft(false)}
            onPointerOut={() => toggleLeft(false)}>
            left
          </div>

          <div
            className="text-center w-16 border border-white-500 hover:bg-blue-700 text-white font-bold py-2 rounded select-none"
            onPointerDown={() => toggleRight(true)}
            onPointerUp={() => toggleRight(false)}
            onPointerLeave={() => toggleRight(false)}
            onPointerOut={() => toggleRight(false)}>
            right
          </div>

        </div>


        <div class="flex justify-center items-center">

          <div
                className="text-center w-16 border border-white-500 hover:bg-blue-700 text-white font-bold py-2 rounded select-none"
                onPointerDown={() => toggleDown(true)}
                onPointerUp={() => toggleDown(false)}
                onPointerLeave={() => toggleDown(false)}
                onPointerOut={() => toggleDown(false)}>
                down
          </div>

        </div>

      </Html>


      <group ref={group} dispose={null}>
        <group scale={props.scale}>
          <primitive object={gltf.scene} position={props.position} rotation={props.rotation} scale={props.scale} />
        </group>
      </group>


    </>
  )

}
// Model02 End /////////////////////////////////////////////////////////////////////////////

// Model03 Start //////////////////////////////////////////////////////////////////////////
const Model03 = (props) => {

  const [action, setAction] = useState('Walking')
  const [previousAction, setPreviousAction] = useState('')

  const [up, toggleUp] = useState(false)
  const [left, toggleLeft] = useState(false)
  const [right, toggleRight] = useState(false)
  const [down, toggleDown] = useState(false)


  const group = useRef()

  const gltf = useGLTF(ModelPath03)

  const { actions } = useAnimations(gltf.animations, group)

  useEffect(() => {
    //console.log(actions) // find out the name of your action
    if (previousAction) {
      actions[previousAction].fadeOut(0.2)
      actions[previousAction].stop()
    }

    actions[action].play()
    actions[action].fadeIn(0.2)
  }, [action, actions])



  useFrame((state) => {
    gltf.scene.rotation.x += up ? -0.1 : 0
    gltf.scene.rotation.x += down ? 0.1 : 0
    gltf.scene.rotation.y += left ? -0.1 : 0
    gltf.scene.rotation.y += right ? 0.1 : 0
  }, [])

  return (
    <>

      <Html fullscreen>

        <h3 className="text-center text-2xl font-medium text-blue-500">Hello World! This is Robot!</h3>

        <div class="flex justify-center gap-1">
            <div className="text-center w-18 border border-white-500 hover:bg-blue-700 text-white font-bold py-2 rounded select-none"
            onClick={() => {
              setPreviousAction(action)
              setAction('Idle')
            }}>
            Idle
          </div>
          <div className="text-center w-18 border border-white-500 hover:bg-blue-700 text-white font-bold py-2 rounded select-none"
            onClick={() => {
              setPreviousAction(action)
              setAction('Walking')
            }}>
            Walking
          </div>
          <div className="text-center w-18 border border-white-500 hover:bg-blue-700 text-white font-bold py-2 rounded select-none"
            onClick={() => {
              setPreviousAction(action)
              setAction('Running')
            }}>
            Running
          </div>
          <div className="text-center w-18 border border-white-500 hover:bg-blue-700 text-white font-bold py-2 rounded select-none"
            onClick={() => {
              setPreviousAction(action)
              setAction('Dance')
            }}>
            Dance
          </div>
          <div className="text-center w-18 border border-white-500 hover:bg-blue-700 text-white font-bold py-2 rounded select-none"
            onClick={() => {
              setPreviousAction(action)
              setAction('Death')
            }}>
            Death
          </div>
          <div className="text-center w-18 border border-white-500 hover:bg-blue-700 text-white font-bold py-2 rounded select-none"
            onClick={() => {
              setPreviousAction(action)
              setAction('Sitting')
            }}>
            Sitting
          </div>
          <div className="text-center w-18 border border-white-500 hover:bg-blue-700 text-white font-bold py-2 rounded select-none"
            onClick={() => {
              setPreviousAction(action)
              setAction('Standing')
            }}>
            Standing
          </div>
          <div className="text-center w-18 border border-white-500 hover:bg-blue-700 text-white font-bold py-2 rounded select-none"
            onClick={() => {
              setPreviousAction(action)
              setAction('Jump')
            }}>
            Jump
          </div>
          <div className="text-center w-18 border border-white-500 hover:bg-blue-700 text-white font-bold py-2 rounded select-none"
            onClick={() => {
              setPreviousAction(action)
              setAction('Yes')
            }}>
            Yes
          </div>
          <div className="text-center w-18 border border-white-500 hover:bg-blue-700 text-white font-bold py-2 rounded select-none"
            onClick={() => {
              setPreviousAction(action)
              setAction('No')
            }}>
            No
          </div>
          <div className="text-center w-18 border border-white-500 hover:bg-blue-700 text-white font-bold py-2 rounded select-none"
            onClick={() => {
              setPreviousAction(action)
              setAction('Wave')
            }}>
            Wave
          </div>
          <div className="text-center w-18 border border-white-500 hover:bg-blue-700 text-white font-bold py-2 rounded select-none"
            onClick={() => {
              setPreviousAction(action)
              setAction('Punch')
            }}>
            Punch
          </div>
          <div className="text-center w-18 border border-white-500 hover:bg-blue-700 text-white font-bold py-2 rounded select-none"
            onClick={() => {
              setPreviousAction(action)
              setAction('ThumbsUp')
            }}>
            ThumbsUp
          </div>
        </div>

        <div className="relative h-screen w-screen bg-opacity-0 bg-green-500">

          <div
            className="absolute top-2 m-auto left-0 right-0 text-center w-16 border border-white-500 hover:bg-blue-700 text-white font-bold py-2 rounded select-none"
            onPointerDown={() => toggleUp(true)}
            onPointerUp={() => toggleUp(false)}
            onPointerLeave={() => toggleUp(false)}
            onPointerOut={() => toggleUp(false)}>
            up
          </div>


          <div
            className="absolute bottom-48 left-1/3 text-center w-16 border border-white-500 hover:bg-blue-700 text-white font-bold py-2 rounded select-none"
            onPointerDown={() => toggleLeft(true)}
            onPointerUp={() => toggleLeft(false)}
            onPointerLeave={() => toggleLeft(false)}
            onPointerOut={() => toggleLeft(false)}>
            left
          </div>


          <div
            className="absolute bottom-48 right-1/3 text-center w-16 border border-white-500 hover:bg-blue-700 text-white font-bold py-2 rounded select-none"
            onPointerDown={() => toggleRight(true)}
            onPointerUp={() => toggleRight(false)}
            onPointerLeave={() => toggleRight(false)}
            onPointerOut={() => toggleRight(false)}>
            right
          </div>


          <div
            className="absolute bottom-44 m-auto left-0 right-0 text-center w-16 border border-white-500 hover:bg-blue-700 text-white font-bold py-2 rounded select-none"
            onPointerDown={() => toggleDown(true)}
            onPointerUp={() => toggleDown(false)}
            onPointerLeave={() => toggleDown(false)}
            onPointerOut={() => toggleDown(false)}>
            down
          </div>

        </div>

      </Html>


      <group ref={group} dispose={null}>
        <group scale={props.scale}>
          <primitive object={gltf.scene} position={props.position} rotation={props.rotation} scale={props.scale} />
        </group>
      </group>


    </>
  )

}
// Model03 End ////////////////////////////////////////////////////////////////////////////

ReactDOM.render(
  <BrowserRouter>
    <App />
  </BrowserRouter>,
  document.getElementById("root")
);


ここまでのコード作成のために参考にさせていただいたサイトその2

その1にひきつづき、React RouterのNavLinkにTailwindCSSを適応させるために以下のサイトを参考にさせていただきました
React Router Active NavLink with Tailwind CSS


3Dオブジェクトを表示するスペースの幅と高さを設定するために以下のサイトを参考にさせていただきました
Three-fiber canvas size
React-three/fiber canvas's width and height is being overridden and I don't know why
Tailwind CSS 入門と実践 > Tailwind CSS でよく使うクラス(サイズ編)
出来る限り短く説明する React + Tailwind CSS 入門(忙しい人向け)


ボタンを表示・配置して機能させるために自分のサイトも含めた以下のサイトを参考にさせていただきました
Tailwind CSS 入門と実践 > Tailwind CSS でよく使うクラス(サイズ編)
Tailwind CSS でよく使うクラス(レイアウト編)
Tailwind CSS でよく使うクラス(余白編)
tailwindcssで要素を画面の中央に配置する方法
フリーターからプログラマになったオヤジの備忘録
CodeSandboxでReact Three Fiber実験その1 > React Three Fiber Practice03 GUI Test by HTML&CSS


背景色の設定用に以下のサイトを参考にさせていただきました
tailwindcss > Background Color


opacity使用時に以下のサイトを参考にさせていただきました
Background Opacity


tailwindでのabsoluteを使用したボタンのレイアウトを設定するために以下のサイトを参考にさせていただきました
tailwindcssのposition / z-index / overflow について
CSSのpositionを総まとめ!absoluteやfixedの使い方は?
tailwindcss > Width
tailwindcss > Height
Tailwind CSS の Top / Right / Bottom / Left ユーティリティの注意点とエラー


center absolute positionについて以下のサイトを参考にさせていただきました
Can't center absolute position (Tailwind.css)
Center an absolute element in TailwindCSS


React Routerでの切替用画面で表示する3Dモデルについて以下の自分のコードを参考にしました
React Three Fiber 8.15.12 Test09 Textured Box
React Three Fiber 8.15.12 Test10 Textured Plane
React Three Fiber 8.15.12 Test11 Textured Sphere
React Three Fiber 8.16.1 glTF Animation Test04
React Three Fiber 8.16.1 glTF Animation Test05
React Three Fiber 8.16.1 glTF Animation Test07
React Three Fiber 8.16.1 glTF Animation Change Test01
React Three Fiber 8.16.1 glTF Animation Change Test04
React Three Fiber 8.16.1 glTF Animation Change Test05


メモ
今後のTailwind CSSによるレスポンシブ設定用に以下のサイトを参考にさせていただく、かもです
Responsive Design
Customizing Screens
Tailwindによるレスポンシブ対応
Tailwind CSSでレスポンシブなコーディングをする


継続作業

underconstruction


この記事が気に入ったらサポートをしてみませんか?