Three.jsがNext.js環境で動くか、試したところ、動きました!Three.jsのドキュメントのGet Startedがベースですが、個人的にやってみたかったこともいくつか追加。任意のタグの中にcanvasタグを配置、画面リサイズに対応してcanvasタグもリサイズ、ライティング設定です。それではいきましょう!
【目次】
※参考:【WebGL】Three.jsを習得するためにやったことまとめ(随時更新) - Qiita
※参考:【React】ReactでWebアプリを作るシリーズまとめ(随時更新) - Qiita
つくったもの
黒い空間の中で箱がクルクル回っているだけ(自分にとっては大きな一歩!)
ソースコード
※参考:next-three-js-test/Inner_001.tsx at main · ryo-i/next-three-js-test · GitHub
Three.jsのインストール
以前作成した「Next.jsスターターキット」をクローンしてベースにする。
※参考:GitHub - ryo-i/next-app-started
Three.jsをインストール
npm install three
TypeScript用の@typesもインストール
npm install --save-dev @types/three
※参考:Next.jsにThree.jsを導入して表示させるまでの手順【TypeScript】 - Qiita
※参考:three.js docs
インポート
React機能のimport
import React, { useState, useEffect, useRef } from 'react';
最終的にuseState, useEffect, useRefも使うことになった。
Three.jsのimport
import * as THREE from 'three/src/Three';
ドキュメントにあるようなimport * as THREE from 'three'
という書き方だとUncaught Errorになった。
node_modulesの中のReactフォルダなどを見ると直下にindex.jsがある。しかしthreeフォルダ直下にはindex.jsがない。
Three.jsのドキュメントに「ES modules依存」云々の記載がある。また、パス付きで下層ファイルをインポートしている例もあった。そのためthree/src/Three
とパス付きでインポートしたらうまくいった。
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'
※参考:three.js docs
その他、調べたり試したりしたこと(うまくいかず)
※参考:3D canvasを表示 · Issue #2 · ryo-i/next-three-js-test · GitHub
コンポーネント
// Component function Inner() { const [canvasSize, setCanvasSize] = useState(0); const figureElm = useRef(null); const changeCanvasSize = (canvasElmWidth) => { if (canvasElmWidth < 900) { setCanvasSize(canvasElmWidth); } else { setCanvasSize(900); } } useEffect(() => { const canvasElmWidth = figureElm.current.clientWidth; // console.log('canvasElmWidth(load)', canvasElmWidth); changeCanvasSize(canvasElmWidth); window.addEventListener('resize', () => { const canvasElmWidth = figureElm.current.clientWidth; // console.log('canvasElmWidth(resize)', canvasElmWidth); changeCanvasSize(canvasElmWidth); }); }, [0]); useEffect(() => { // three.js const scene = new THREE.Scene(); const camera = new THREE.PerspectiveCamera( 35, canvasSize / canvasSize, 0.1, 1000 ); const renderer = new THREE.WebGLRenderer(); renderer.setSize( canvasSize, canvasSize ); if (figureElm.current.firstChild) { figureElm.current.removeChild( figureElm.current.firstChild ); } figureElm.current.appendChild( renderer.domElement ); const geometry = new THREE.BoxGeometry( 1, 1, 1 ); const material = new THREE.MeshStandardMaterial( { color: 0xff0000 } ); const cube = new THREE.Mesh( geometry, material ); scene.add( cube ); camera.position.z = 5; const light = new THREE.HemisphereLight( 0xffffbb, 0x080820, 1 ); scene.add( light ); function animate() { requestAnimationFrame( animate ); cube.rotation.x += 0.01; cube.rotation.y += 0.01; renderer.render( scene, camera ); }; animate(); }, [canvasSize]); // JSX return ( <Figure ref={figureElm}></Figure> ); }
基本的には下記のThree.jsドキュメント「はじめてみよう」のコードがベースになっている。
※参考:three.js docs
それに加えて下記の機能を追加した
- 任意のタグの中にcanvasタグを配置
- 画面リサイズに対応してcanvasタグもリサイズ
- ライティング設定
任意のタグの中にcanvasタグを配置
Three.jsドキュメントのままだとフッターより下、bodyの閉じタグあたりにcanvas要素が追加された。これを任意のタグの中に配置したい。
current.appendChild()を使う方法が多そうに思ったのでそちらの方法をとった
※参考:three.js docs
(他にWebGLRenderer()のcanvasオプションで指定する方法もあった)
※参考:three.js docs
figureElm
にuseRef()
を設定(初期値はnull
)
const figureElm = useRef(null);
figureElm
にcanvasタグを追加
if (figureElm.current.firstChild) { figureElm.current.removeChild( figureElm.current.firstChild ); } figureElm.current.appendChild( renderer.domElement );
後述するcanvasリサイズ設定によって、画面をリサイズする再レンダリングのたびにcanvasタグが次々と作られてしまった。
appendChild()時にfirstChildでcanvas要素の存在チェックしてremoveChildで削除する方法だとうまくいった
※参考:Three.jsでCSS3の3D描画ができた! | スターフィールド株式会社
画面リサイズに対応してcanvasタグもリサイズ
canvasタグを固定値ではなく、画面幅を変えるたびにcanvasタグをリサイズしたい。フックを設定して連携した。
フックでcanvasSize
を設定(初期値は0)
const [canvasSize, setCanvasSize] = useState(0);
changeCanvasSize()
関数
const changeCanvasSize = (canvasElmWidth) => { if (canvasElmWidth < 900) { setCanvasSize(canvasElmWidth); } else { setCanvasSize(900); } }
引数をチェックし、900より小さければそのサイズをcanvasSize
に登録(大きければ900を登録)
ページ幅を監視し、変化があったらchangeCanvasSize()
を実行
useEffect(() => { const canvasElmWidth = figureElm.current.clientWidth; // console.log('canvasElmWidth(load)', canvasElmWidth); changeCanvasSize(canvasElmWidth); window.addEventListener('resize', () => { const canvasElmWidth = figureElm.current.clientWidth; // console.log('canvasElmWidth(resize)', canvasElmWidth); changeCanvasSize(canvasElmWidth); }); }, [0]);
canvasSize
に変化があったらcanvasSize
の値をsetSize()
でcanvasタグをリサイズ
useEffect(() => { // three.js const scene = new THREE.Scene(); const camera = new THREE.PerspectiveCamera( 35, canvasSize / canvasSize, 0.1, 1000 ); const renderer = new THREE.WebGLRenderer(); renderer.setSize( canvasSize, canvasSize ); // 中略 }, [canvasSize]);
ライティング設定
Three.jsの見本だとこのような影のない見た目になった。
これは「MeshBasicMaterial」というライティング設定が不要だがベタ一色になるマテリアルのため
※参考:three.js docs
その他マテリアル
※参考:【Three.js】いろいろなマテリアルを貼ってみる - クモのようにコツコツと
これを影のある「MeshStandardMaterial」に変更したい
※参考:three.js docs
const material = new THREE.MeshStandardMaterial( { color: 0xff0000 } );
ただ変えるだけだと画面が真っ暗になってしまうため、ライティング設定を追加する
※参考:three.js docs
const light = new THREE.HemisphereLight( 0xffffbb, 0x080820, 1 ); scene.add( light );
これで影付きの回る箱が現れた!!
おわりに
ということで、Next.js環境でThree.jsを動かすことができました。問題なく動くことがわかってよかったです♪
今後やりたいことはいっぱいあって、ここにプールしています。順次、ページを増やして記録していきたく思います。
※参考:今後のやりたいこと · Issue #3 · ryo-i/next-three-js-test · GitHub
- Tree.jsとしてもどんどん凝ったことをしていきたい
- Reactフックでinputタグと連携させたい
- GSAPのアニメにも連携させたい
- Tone.jsの音と連携さたい
- Cannon.jsの物理演算と連携させたい
- ユーザーの操作に反応するインタラクティブなものを作りたい
- canvas上のユーザー操作で得た情報を外部のタグに連携したい
- PCとスマホのイベント挙動の違いを理解したい
- mathオブジェクトのランダムや三角関数と連携
- パーティクルアニメ
- 文字を立体化させる
- APIから読んだ文字を読み込む
- シェードでマテリアルの形を変えたい(モデリングはゼロから可能?)
- Blenderのモデリングデータを読み込む
※参考:【WebGL】Three.jsを習得するためにやったことまとめ(随時更新) - Qiita
※参考:【React】ReactでWebアプリを作るシリーズまとめ(随時更新) - Qiita