Next.jsでやる必要は全くなかったんですが、手ぐせでcreate-next-app
をしてしまったので、Next.js上でThree.jsを学んでいます。
もし開発に制約がないのであればcreate-react-app
で始める事をお勧めします。
あと、いきなりreact-three-fiberを使っての開発だとわからないことやドキュメントを読み進めにくい部分もあると思ったので、Parcel + three.jsの環境も用意して検証しながら進めてます。
モジュールのimportができない
three.jsのmoduleをimportしようとするとCannot use import statement outside a module
というエラーが出ます。
今回は、three/examples/jsm/controls/OrbitControls
をimportする部分などで発生していたので、next/dynamic
で解決しました。
/* _app.tsx */
import { AppProps } from 'next/app'
import { Canvas } from 'react-three-fiber'
import dynamic from 'next/dynamic'
const Controls = dynamic(() => {
return import('components/Controls')
}, { ssr: false })
export default function App({ Component, pageProps }: AppProps) {
return (
<Canvas>
<Controls />
<gridHelper />
<Component {...pageProps} />
</Canvas>
)
}
Suspenseは便利だけどまだ安定リリースはされてない
<Suspense>
は読み込みが終わるまでfallbackを表示し、読み込みが終わったら中のComponentを表示してくれます。
今回はdynamic importを多様するので使ってみることにしましたが、ドキュメントを読むと2021/2/4時点でまだ実験的な機能だということです。
サスペンスを使ったデータ取得(実験的機能)
import { Suspense } from 'react'
import dynamic from 'next/dynamic'
const SampleModel = dynamic(() => {
return import('components/SampleModel')
}, { ssr: false })
export default function Page() {
return (
<Suspense fallback={null}>
<SampleModel />
</Suspense>
)
}
glbファイルを読み込んで表示する
glbファイルはpublic/gltf/Xbot.glb
に入れています。読み込むためにnext.config.js
にfile-loaderの設定をします。
/* next.config.js */
module.exports = {
webpack: (config, options) => {
config.module.rules.push({
test: /\.(glb|gltf)$/,
use: {
loader: 'file-loader',
}
})
return config
},
}
表示だけであれば<primitive>
のobjectにsceneを渡せば表示されます。
走らせる場合の設定に関してはまだ完全に理解できてるわけではありません。
今回のモデルの場合は、gltf.animations
の中のnameが「run」というのがたまたま4番目にきていたのでgltf.animations[3]
を決め打ちで入力しています。
しかし他の動きに変更すると挙動がおかしくなるanimationもあるので、モデル側の設定とThree.js側の設定が噛み合っていないのだと思います。あやしいのはclockですがまだどうしたらいいのかわかっていません。
/* components/SampleModel.tsx */
import { useState, useEffect, useRef } from 'react'
import * as THREE from 'three'
import { useLoader, useFrame } from 'react-three-fiber'
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'
const position = [0, 0, 0]
const scale = [1, 1, 1]
type Props = {}
const SampleModel: React.FC<Props> = (props) => {
const gltf = useLoader(GLTFLoader, "/gltf/Xbot.glb")
const clock = useRef(new THREE.Clock());
const [mixer] = useState(() => new THREE.AnimationMixer(gltf.scene))
useEffect(() => {
if (!mixer) return
const action = mixer.clipAction(gltf.animations[3])
action.play()
}, [mixer, gltf])
useFrame(() => {
mixer.update(clock.current.getDelta())
})
return (
<primitive
object={gltf.scene}
dispose={null}
position={position}
scale={scale}
/>
)
}
export default SampleModel
まとめ
今までのWebは平面上(二次元)上にコンポーネントを配置していくので、三次元のイメージがし難く、二次元のイメージに引っ張られ結構苦戦しています。
まだ学習は続ける予定なのでreact-three-fiberで詰まったこととかあったら、ここに追記していきます。