Three.js を React ( Next.js ) で実装する
Introduction
Three.js は言わずと知れた JavaScript 製の WebGL グラフィックスライブラリだ
公式ドキュメントは充実しているし、何といつの日からか日本語でも読めるようになっている
私は Three.js を勉強していく上で、これを React ( Next.js ) 上で動かせたら随分楽だろうになあと思っている
理由は、最近の React app ( Next app ) の create コマンドがなかなか行き届いていて、すぐにローカルの Web アプリ環境を立ち上げることができるのと、 VSCode の拡張機能の充実から、 Three.js もそうだが、オブジェクトのプロパティ等の補完をいい感じに出してくれること、 TypeScript ・・などなどがある ( Three.js も @types のパッケージがあるので有り難い )
あとは React ( Next.js ) 使いたい個人的な理由など
目的
公式を含めた Three.js のチュートリアルをスムーズに学習するための書き換え
React の方式で楽に記述できそうなところを積極的に変えていくが、チュートリアルの記述方法から逸脱するほどに過度に React 側に寄せることはしない
React ( Next.js ) を使うなら react-three-fiber と drei を使うと良い、ということのようだが、コレ自体はとても興味があるもののまだ Three.js のスの字もわかっていないのでまだ手を出さないでおこうかと思っている
さっそく実装例
これが基本の形となるだろう
コードは ics.media の Three.js 入門「Three.jsのマテリアルの基本」を主に使わせてもらい、それを React Hooks で書き換えた
この入門記事は連載形式になっていて Three.js の基本から応用までを一通り学べるようになっている
書かれたのは 4 年ほど前だが定期的にメンテナンスをしている部分もあるし、内容自体は今も変わっていないので問題なく利用できる
import { useEffect, useRef } from 'react'
import type { NextPage } from 'next'
import * as THREE from 'three'
const Home: NextPage = () => {
const mountRef = useRef<HTMLDivElement>(null)
useEffect(() => {
const w = 960
const h = 540
const renderer = new THREE.WebGLRenderer()
const elm = mountRef.current
elm?.appendChild(renderer.domElement)
renderer.setPixelRatio(window.devicePixelRatio)
renderer.setSize(w, h)
const scene = new THREE.Scene()
const camera = new THREE.PerspectiveCamera(45, w / h, 1, 10000)
camera.position.set(0, 0, +1000)
const geometry = new THREE.SphereGeometry(300, 30, 30)
const loader = new THREE.TextureLoader()
const texture = loader.load('/texture/earthmap1k.jpg')
const material = new THREE.MeshStandardMaterial({
map: texture,
})
const mesh = new THREE.Mesh(geometry, material)
scene.add(mesh)
const directionalLight = new THREE.DirectionalLight(0xffffff)
directionalLight.position.set(1, 1, 1)
scene.add(directionalLight)
const tick = () => {
mesh.rotation.y += 0.01
renderer.render(scene, camera)
requestAnimationFrame(tick)
}
tick()
return () => {
elm?.removeChild(renderer.domElement)
}
}, [])
return (
<div ref={mountRef} />
)
}
export default Home
ポイントは useRef()
と useEffect()
だ
コンポーネントがマウントされた後でないと canvas
のサイズが取得できないため useEffect()
内で Three.js 関連の描画処理を行っている
appendChild()
で canvas
をマウントし、最後は return
の中で removeChild()
して解放する
ref
を使って canvas
をマウントする場所を特定するのだが TypeScript では .current
が null
になる可能性があるので ?.
を忘れない
また removeChild()
の中で .current
をそのまま使うと
と Warning が出てしまう
「mountRef.current
は値が変わってしまうかも知れないから変数にコピーして使ってね」とあるので頭の方で
elm = mountRef.current
とした
まとめ
今回は素の JavaScript で書かれている Three.js チュートリアルを React ( Next.js ) の中で素直に実行できるように最低限の書き換えを行った
React の特性を生かしたもっと動的な実装方法はいくらでもやりようはあるが追い追い順応していこうと思う
Discussion