#はじめに
初めまして。
Quarterと申します。
初投稿ですが、拙い文章にお付き合いください。
Three.js Advent Calendar 2019 17日目です。
仕事でThree.jsを使用したアプリケーションを作成しているところですが、技術選定の段階でQiitaには色々とお世話になったので、作成過程でつまづいたことを情報共有できたらと思い投稿します。
#Three.js + React + TypeScript
実のところ、仕事であんまりWebをやったことが無かったのですが、1年ぐらい前にReact + TypeScriptをちょっと使ったことがあったので、とりあえず Three.js + React + TypeScript の構成で行こうと決めました。
じゃぁReact上でThree.jsを使うには?ということで色々調べている過程でQiita上でhppRCさんの投稿を見つけて、「これは良さそうだ」とreact-three-fiberを使うことにしました。
今回は以下のそれぞれのバージョンで検証した内容となります。
- Three.js 0.110.0
- React 16.12.0
- react-three-fiber 3.0.15
- TypeScript 3.7.2
#OrbitControlsを使いたい!
3Dモデルの確認するためにOrbitControlsはどうしても使いたかったのですが、全然書き方が分からずにネットを彷徨うことに。
色々と探していると本家(react-three-fiber)のissue #27で以下のようなコードを発見しました。
import React, { useRef } from 'react'
import { extend, useThree, useFrame } from 'react-three-fiber'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
extend({ OrbitControls })
const Controls = props => {
const { camera } = useThree()
const controls = useRef()
useRender(() => controls.current && controls.current.update())
return <orbitControls ref={controls} args={[camera]} {...props} />
}
extend({ OrbitControls })
でReactで使える <orbitControls />
エレメントを作成して、useThree()
でカメラとか取ってきてパラメータにセットする。なるほどなるほど。
で、TypeScriptで一部修正したのが以下のコード。
import React, { useRef } from 'react';
import { extend, useThree, useFrame } from 'react-three-fiber';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
extend({ OrbitControls });
export const Controls: React.FC = () => {
const controlsRef = useRef<OrbitControls>();
const { camera, gl } = useThree();
useFrame(() => controlsRef.current?.update());
return (
<orbitControls
ref={controlsRef}
args={[camera, gl.domElement]}
/>
);
}
で、いざ実行!と思ったらビルドが通りませんでした...。
TS2339: Property 'orbitControls' does not exist on type 'JSX.IntrinsicElements'.
型JSX.IntrinsicElements にプロパティorbitControls はありません...、なんじゃそりゃ?
で、「OrbitControls TypeScript」で検索しているとやはり本家のissueにぶつかりました。
今度はissue #130です。
declare module JSX{
interface IntrinsicElements{
"group": any,
"geometry": any,
"lineBasicMaterial": any,
"mesh": any,
"octahedronGeometry": any,
"meshBasicMaterial": any,
"orbitControls": any //I added this part
}
}
後で調べて分かりましたが、これでinterfaceを拡張しているんですね。
ただ、この書き方だとVSCodeに怒られたので少し修正しました。
最終的に以下のようなコードになりました。
import React, { useRef } from 'react';
import { ReactThreeFiber, extend, useThree, useFrame } from 'react-three-fiber';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
extend({ OrbitControls });
// インターフェイスIntrinsicElementsにorbitControls の定義を追加
declare global {
namespace JSX {
interface IntrinsicElements {
orbitControls: ReactThreeFiber.Node<OrbitControls, typeof OrbitControls>
}
}
}
type ControlProps = {
isControl: boolean;
}
export const Controls: React.FC<ControlProps> = (props) => {
const controlsRef = useRef<OrbitControls>();
const { camera, gl } = useThree();
useFrame(() => {
controlsRef.current?.update();
});
return (
<orbitControls
ref={controlsRef}
args={[camera, gl.domElement]}
enabled={props.isControl}
enableZoom={true}
zoomSpeed={1.0}
enableRotate={true}
rotateSpeed={1.0}
enablePan={true}
panSpeed={2.0}
minDistance={0}
maxDistance={Infinity}
minPolarAngle={0}
maxPolarAngle={Math.PI}
/>
);
}
テスト用のOrbitControlの機能をON/OFFするパラメータ1個を追加し、後のパラメータは完全固定にしてあります。
実際に使うときは以下のような感じになります。
return (
<Canvas>
<ambientLight intensity={1.0} color={new Color(0xffffff)} />
<Controls isControl={true} />
<mesh>
<boxBufferGeometry attach="geometry" args={[1,1,1]}/>
<meshNormalMaterial attach="material" />
</mesh>
</Canvas>
);
#おわりに
Three.js単体でも日本語の情報が少ないのに、Reactを組み合わせると更に情報が減るなぁ、というのを今現在実感しています。
もし同じ組み合わせで悩んでいる人の解決の糸口になれば幸いです。