DOMを使って3D表現を実現!three.jsの機能拡張ライブラリ CSS3DRenderer.js の使い方

posts_photo

three.js
最近three.jsを勉強していまして。
ベーシックな部分はなんとなくわかるようになってきたんですが、実際に制作時に使えるものを作るにはサンプルに入っている機能拡張ライブラリを見ていくのが近道かなぁと思って気になったものをピックアップしてみてる今日この頃です。
(余談ですが、three.jsの拡張ライブラリってサンプルの中にしれっと入っていて個々のライブラリに対する紹介みたいのがあんまりないのすごくわかりづらいなぁと思います……)

今回紹介するのは、CSSでデザインしたDOM要素をそこそこ手軽に3D空間で動かせるようになる拡張ライブラリCSS3DRenderer.jsです。
three,jsのexamplesで言うと下記のURLになります。
CSS3d periodictable

ざっくりとその機能を説明すると、CSSのtransformを使ってDOM要素を動かし、座標や回転軸に関しての計算をthree.jsに任せることによって疑似的に3D空間で動いているように見せるライブラリです。
Canvasに描かれるWebGLとは違ってDOM要素を扱うのでモデリングした立体を表示するとかは難しいです。基本はペラペラの平面要素を3D空間でぶん回す動きになります。

魅力としてはDOMなので普通にリンクを張ったり出来ることですね!実際のサイトで使うとなるとリンク超大事です。
動きの部分以外はCSSや画像で普通にスタイリング出来るのでthree.js初心者でも見た目整えるのが簡単なのがいいです。

用意するもの

まずはthree.jsからファイルをダウンロードしましょう。
そしてのその中にあるサンプルである/examples/css3d_periodictable.htmlに読み込まれている下記のjsファイルを読み込みます。コアであるthree.js、アニメーション補助のtween.js、視点移動補助のTrackballControls.js、そして今回取り上げるCSS3DRenderer.jsの4つが最低限です。
ここに見た目を整えるためのcssや、処理を記述するためのscript.jsなどを加えてサンプルが出来ています。

<script src="js/three.min.js" type="text/javascript"></script>
<script src="js/tween.min.js" type="text/javascript"></script>
<script src="js/TrackballControls.js" type="text/javascript"></script>
<script src="js/CSS3DRenderer.js" type="text/javascript"></script>

基本的な使い方

htmlに関してはDOMを格納するためのタグがあるだけで良いです。
用途に応じてオブジェクトの整列を切り替えるためのボタンなどを追加していくと良いでしょう。

<div id="container"></div>

<!--オブジェクトの整列を切り替えるためのボタン-->
<div id="menu">
  <button id="table">TABLE</button>
  <button id="sphere">SPHERE</button>
</div>

CSSの方は特になくても良いのですが、追加するDOMオブジェクトに対してstyleを当ててやると見栄えが良くなります。three.jsのサンプルに入っているCSSにはjsで生成している.elementに対してのstyleが記述してあります。

さて、ここからは今回のメインであるjsの記述を解読していこうと思います。コード自体が長いのでポイントとなる部分だけ抜粋して見ていきましょう。
まずは初期設定的な部分から。

//カメラやシーンなどを設定する変数
var camera, scene, renderer;
var controls;

//objectsが実際に目に見えている要素を格納、targetsが移動させる次の整列内容を格納した変数です。
//サンプルではtable,sphere,helix,gridと4パターンの並び順が実装されています。
var objects = [];
var targets = { table: [], sphere: [], helix: [], grid: [] };

//init()でDOMの生成から整列の定義まで一通り計算を行います。
init();
animate();

次はinit()の中身を見ていきます。

//three.js定番のカメラとシーンの初期設定
camera = new THREE.PerspectiveCamera( 40, window.innerWidth / window.innerHeight, 1, 10000 );
camera.position.z = 2500;
scene = new THREE.Scene();

// table
// サンプルファイルの最初の記述してあるtable変数を使ってfor文を回していきます。
//i+5なのは変数は5個の値をプロパティとする一つの塊として扱うからです。
for ( var i = 0; i < table.length; i += 5 ) {
  //DOMを生成していく。
  var element = document.createElement( 'a' );
  element.className = 'element';
  element.style.backgroundColor = 'rgba(0,127,127,' + ( Math.random() * 0.5 + 0.25 ) + ')';

  //elementの中に入れる番号生成
  var number = document.createElement( 'div' );
  number.className = 'number';
  number.textContent = (i/5) + 1;
  element.appendChild( number );

  //elementの中に入れる個別文言用のDOM生成
  var symbol = document.createElement( 'div' );
  symbol.className = 'symbol';
  symbol.textContent = table[ i ];
  element.appendChild( symbol );
  var details = document.createElement( 'div' );
  details.className = 'details';
  details.innerHTML = table[ i + 1 ] + '<br>' + table[ i + 2 ];
  element.appendChild( details );

  //生成したDOM要素をthree.jsで扱えるようなオブジェクトとして登録
  //初期位置をランダム配置
  var object = new THREE.CSS3DObject( element );
  object.position.x = Math.random() * 4000 - 2000;
  object.position.y = Math.random() * 4000 - 2000;
  object.position.z = Math.random() * 4000 - 2000;
  
  //シーンに追加してオブジェクトをまとめた配列に格納。
  scene.add( object );
  objects.push( object );
  
  
  //ここからはtableの並び順の情報を事前登録。
  //最初にまとめてtargetsの中にオブジェクトの座標情報を格納することで毎回の計算を避ける。
  var object = new THREE.Object3D();
  object.position.x = ( table[ i + 4 ] * 140 ) - 1260;
  object.position.y = - ( table[ i + 3 ] * 180 ) + 600;
  targets.table.push( object );
}

このtableの並び順を登録するところまでがわかれば、その後に記述してある下記のような部分はそれぞれの並び順を登録するための処理だと理解できます。

// sphere
var vector = new THREE.Vector3();
for ( var i = 0, l = objects.length; i < l; i ++ ) {
 //割愛
}

// helix
var vector = new THREE.Vector3();
for ( var i = 0, l = objects.length; i < l; i ++ ) {
 //割愛
}

// grid
for ( var i = 0, l = objects.length; i < l; i ++ ) {
 //割愛
}

それぞれの並び方の情報を入力したらCSS3Rndererとしての描画領域のDOMを追加する処理を行い、あとは視点コントロールやボタンのクリックイベントを設定していくだけです。

//描画領域としてのDOMを設定。
renderer = new THREE.CSS3DRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.domElement.style.position = 'absolute';
document.getElementById( 'container' ).appendChild( renderer.domElement );

//視点コントロールの設定
controls = new THREE.TrackballControls( camera, renderer.domElement );
controls.rotateSpeed = 0.5;
controls.minDistance = 500;
controls.maxDistance = 6000;
controls.addEventListener( 'change', render );

//並び順を切り替えるボタンのクリックイベント
var button = document.getElementById( 'table' );
button.addEventListener( 'click', function ( event ) {
	transform( targets.table, 2000 );
}, false );

//shere〜gridのボタンイベント記述は割愛します。

//最初にランダム配置から集合する並び順
transform( targets.table, 2000 );
//リサイズ用のイベントを設定
window.addEventListener( 'resize', onWindowResize, false );

init()による初期設定の処理はここまでです。次はその他の関数の設定を見ていきましょう。
まずは整列するためのアニメーションを司るtransform()から。

function transform( targets, duration ) {
  // TWEEN処理が混在しないように一旦全て中止
  TWEEN.removeAll();

  for ( var i = 0; i < objects.length; i ++ ) {
    // 現在地のオブジェクト情報
    var object = objects[ i ];
    // 移動後のオブジェクト情報
    var target = targets[ i ];

    // オブジェクトの位置情報をTween。現在の位置からtargetに記録された位置へ。
    new TWEEN.Tween( object.position )
      .to( { x: target.position.x, y: target.position.y, z: target.position.z }, Math.random() * duration + duration )
      .easing( TWEEN.Easing.Exponential.InOut )
      .start();

    //  オブジェクトの角度情報をTween。vectorで3次元角度ベクトルを入れたものは勝手にrotationとして記録されるらしい。
    new TWEEN.Tween( object.rotation )
      .to( { x: target.rotation.x, y: target.rotation.y, z: target.rotation.z }, Math.random() * duration + duration )
      .easing( TWEEN.Easing.Exponential.InOut )
      .start();
  }

  //thisを見るとwindowオブジェクトだったので、画面全体をアップデートして再レンダリングするためっぽい。
  new TWEEN.Tween( this )
    .to( {}, duration * 2 )
    .onUpdate( render )
    .start();
}

残りは大したことないのでまとめて記述します。

//ウィンドウのリサイズに伴う描画のアップデート
function onWindowResize() {
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize( window.innerWidth, window.innerHeight );
  render();
}

//アニメーションのための再描画環境の設定。
//Tween.updateでTweenの更新、controlsで視点情報の更新をかけ、animationFrameのFPSで常にループさせている。
function animate() {
  requestAnimationFrame( animate );
  TWEEN.update();
  controls.update();
  render();
}

//シーンとカメラの再描画処理。
function render() {
  renderer.render( scene, camera );
}

以上がthree.jsの機能拡張ライブラリであるCSS3DRenderer.jsを使ったサンプルの解説でした!
何か間違いなどありましたらTwitterなどに気軽にリプください。

初めてのThree.js 第2版 ―WebGLのためのJavaScript 3Dライブラリ
初めてのThree.js 第2版 ―WebGLのためのJavaScript 3Dライブラリ
-Amazonでみる