Node.js(Express)とthree.jsで一通りウェブサイトつくって公開した記録(初心者向け)

  • 12
    いいね
  • 0
    コメント

この記事はThree.js Advent Calendar 2016の10日目の記事です。

はじめに

  • この記事は「初心者がThree.jsとか使ってなんとか簡単なウェブサイト作って公開した」記録です。なので内容は教科書レベルだと思います。
  • 「Three.jsでとりあえず球体浮かべてみたけどその後何しようか自分でもわからない」「ExpressでHello Worldしてみたけどそれから何したらいいのか」という人の参考になれば光栄です。
  • Three.js Advent Calendarの記事の割にThree.jsに関係ないことが半分くらい占めそうです、すみません。

環境

  • さくらVPS (Apache 2.2.15)
  • Node.js v6.5.0
  • Express v4.13.4
  • three.js r81

ちなみにローカルはWin10です。エディタはAtom、ターミナルはCmder、転送ツールはWinSCP使ってます。

成果物

こちらのウェブサイトです。重いのかスマホで動かない場合がありますがそのうちなんとかしたいです。
http://mojiukasu.toofu.net/

20161105215023.png

ただ入力した文字を空に浮かべて全方位から眺められるだけの、老若男女誰の役にも立たないサイトですが、ネガティブな文字も空に浮かべてみると比較的どうでもよくなるという学びがありました。また、一通りthree.jsやExpressでウェブサイト公開するにあたって色々勉強になったので、以下に記載していきます。

Node.js(Express)での学び

サーバー側で動くapp.jsと最初に訪れるindex.ejsは以下の内容です。

app.js
'use strict';

const express = require('express');
const app = express();
const ejs = require('ejs');

app.set('views', __dirname + '/views');
app.set('view engine', 'ejs');
app.use(express.static( __dirname + '/public'));

app.get('/', (req, res) => {
  res.render('index.ejs');
});

app.get('/main', (req, res) => {
  let rotateText = String(req.query.text);
  if (rotateText.length > 20) {
    rotateText = rotateText.slice(0, 20);
  }
  res.render('main.ejs', {
    rotateText: rotateText
  });
});

app.listen(3000, () => {
  console.log('listening server...');
});
index.ejs(一部)
<form class="form-horizontal" method="get" action="/main">
  <input type="text" name="text" placeholder="浮かす文字を入力してください">
  <button class="btn btn-default">浮かす</button>
</form>

教科書レベルの内容です。ここでのポイントは以下のような感じ。

  • index.ejs内のformに入力した内容をrotateTextとして受け取り、それをmain.ejsに引き渡しています。そのrotateTextをThree.jsで受け取ってなんやかんやしています。
  • rotateText.slice(0, 20)しているのは、単にあまりに長い文字入れられて何か変なことになるのを避けたかっただけです。
  • app.use(express.static( __dirname + '/public'));としておくと、public/配下に入れたjsファイルやcssファイルにejsテンプレートからアクセスできます。

Three.js側での学び

main.ejs内でThree.jsを読み込んでいます。three.nin.jsOrbitControls.jsmain.js(自分で書いたの)public/js/配下に格納しています。

main.ejs(一部)
<script src="/js/three.min.js"></script>
<script src="/js/OrbitControls.js"></script>
<script src="/js/main.js"></script>
<script>
'use strict';
window.addEventListener('DOMContentLoaded', () => {
  const rotateText = '<%=rotateText %>';
  document.body.style.overflow="hidden";
  view(rotateText);
});
</script>

rotateTextにはさっきapp.jsから送った文字列が格納されます。

/js/main.js
'use strict';

let scene, text, hemisphereLight, camera, controls, renderer;

let view = (textString) => {
  // scene
  scene = new THREE.Scene();

  // (中略)

  // SKYDOME
  const skyGeometry = new THREE.SphereGeometry(1600, 60, 40);
  const skyLoader = new THREE.TextureLoader();
  skyLoader.load('/img/skydome.jpg', (texture) => {
    const skyMaterial = new THREE.MeshBasicMaterial({
      map: texture
    });
    const skyBox = new THREE.Mesh(skyGeometry, skyMaterial);
    skyBox.material.side = THREE.BackSide;
    scene.add(skyBox);

    // (中略)

    // text Mesh
    const fontLoader = new THREE.FontLoader();
    fontLoader.load("/js/SOEI-KANA-WebSubsetFont_Regular.json",function(tex){
      const  textGeometry = new THREE.TextGeometry(textString, {
        size:10,
        height: 30,
        curveSegments: 6,
        font: tex,
      });
      textGeometry.computeBoundingBox();
      const centerOffset = -0.5 * ( textGeometry.boundingBox.max.x - textGeometry.boundingBox.min.x );
      const color = new THREE.Color();
      const textMaterial = new THREE.MeshLambertMaterial({color: 0xffffff});
      text = new THREE.Mesh(textGeometry , textMaterial);
      // (中略)
      scene.add(text);

      // (中略)

      renderer = new THREE.WebGLRenderer({antialias: true});
      animate();


    });

  });
}

// (中略)
const animate = () => {
  // (中略)
  render();
}
const render = () => {
  renderer.render(scene, camera);
}

Three.js側で今回勉強になったのが2点。

  • テクスチャのローディング
  • 日本語フォントの使い方

Three.jsの学び① テクスチャのローディング

main.js(天球部分の処理)
const skyGeometry = new THREE.SphereGeometry(1600, 60, 40);
  const skyLoader = new THREE.TextureLoader();
  skyLoader.load('/img/skydome.jpg', (texture) => {
    const skyMaterial = new THREE.MeshBasicMaterial({
      map: texture
    });
    const skyBox = new THREE.Mesh(skyGeometry, skyMaterial);
    skyBox.material.side = THREE.BackSide;
    scene.add(skyBox);
    // 以後各種処理

THREE.TextureLoader().loadpublic/img/に置いた空画像を読み込んで、textureに格納しています。空画像はそこそこの解像度が無いと見た目が厳しいため、そこそこファイルサイズは大きめです。なので空画像を読み込んでからlightやcamera、rendererを設定するよう、そのあたりの処理は全部skyLoader.loadのコールバック内に記述しています。

Three.jsの学び② 日本語フォントの利用

参考記事

main.js(日本語フォント部分の処理)
const fontLoader = new THREE.FontLoader();
    fontLoader.load("/js/SOEI-KANA-WebSubsetFont_Regular.json",function(tex){
      const  textGeometry = new THREE.TextGeometry(textString, {
        size:10,
        height: 30,
        curveSegments: 6,
        font: tex,
      });
      textGeometry.computeBoundingBox();
      const centerOffset = -0.5 * ( textGeometry.boundingBox.max.x - textGeometry.boundingBox.min.x );
      const color = new THREE.Color();
      const textMaterial = new THREE.MeshLambertMaterial({color: 0xffffff});
      text = new THREE.Mesh(textGeometry , textMaterial);
      // (中略)
      scene.add(text);

THREE.FontLoader()でフォントファイルの読み込みを行いますが、一般的なフォントファイル(.ttf)ではなくjsonまたはjsファイルに変換されたものを読み込みます。普通にThree.jsのmasterファイルをダウンロードするとexamples/fonts/配下にHelveticaやOptimaのjsonファイルはありますが、これを読み込んだだけでは日本語ファイルは表示できず「?」と表示されてしまいます。なので日本語フォントの.ttfファイルをjsonに変換したいのですが、そのままだと漢字とかが多すぎてツラいです。

そこで使うのがサブセットフォントメーカー。フォントファイルから特定の文字列だけ抽出してあらたなフォントファイルを作成してくれます。Macでも使えるのかどうかはわかりませんすみません。
capture.PNG
こんな感じで新しい.ttfファイルを作成します。フォントに格納する文字は自由です。自分はひらがなカタカナ英数字記号と漢字60文字くらいでやりました。

ただこれだと.ttfファイルのままなので、これをtypeface.jsでjsonかjsファイルにします。2つほどチェックボックスありますが、とりあえずそのままで大丈夫です。

で、できあがったjsonファイル(ここではSOEI-KANA-WebSubsetFont_Regular.json)をpublic/に格納して、それをfontloaderで読み込むことで日本語フォントを利用できるようになります。

ちなみに、今回のフォントjsonファイルは241KBですが、そこそこあるので無用なバグが起きないように、fontLoader.load()のコールバック内でrender()を実行しています。
なので「空画像読み込む→完了したらフォント読み込む→完了したらレンダリング」という流れになっています。

公開にあたっての学び

Three.jsの話は以上です、すみません。以後はNode.jsで作ったものをどう公開すればいいのか、という話です。

Node.js勉強したてのころは何もわからずロリポップとかのレンタルサーバー上でなんとか動かせないものかと悩んでいたのですが、Node.js触って遊びたいならレンタルサーバーではダメで、さくらVPSとか借りるのをオススメします。256Mプランで十分です。

ここで勉強になったのは以下の2点です。

  • ポート開放
  • サブドメインの適用

公開の学び① ポート開放(iptables)

app.js
app.listen(3000, () => {
  console.log('listening server...');
});

app.jsで上記のように書いてますので、3000番ポートが空いたことになります。3000番ポートを使えるようにします。

# vim /etc/sysconfig/iptables

// 以下を追記
-A SERVICES -p tcp -m tcp --dport 3000 -j ACCEPT

で、3000番ポートからアクセスできるようになります。

公開の学び② サブドメインの適用

(参考:teratailで質問しました。回答者の方ありがとうございました)

例えばローカル仮想環境だと上記でポートを開放した状態でapp.jsを走らせると、http://localhost:3000 でこのページにアクセスできるようになります。しかしVPS上で単純に走らせると、もともと公開してるサイトなどが見れなくなってしまいます。
自分の場合は以下のような条件がありました。

  • toofu.netというドメインを持っていて、http://toofu.net というサイトを同じサーバーから公開している(/var/www/html配下)。これは維持したい。
  • 今回の文字を浮かべるためのサイトは新しくドメインをとるほどでもない。

ということで、http://mojiukasu.toofu.net というサブドメインで公開することにしました。しかし、ドメインの管理コンソールでmojiukasu.toofu.netの向き先をVPSのIPアドレスにしても、普通にnode appすると、http://toofu.net で元々見れていたサイトが見れなくなります。
以下の通りsudo vim /etc/httpd/conf/httpd.confして、toofu.net から来たアクセスとmojiuasku.toofu.net から来たアクセスを振り分けないといけません。

/etc/httpd/conf/httpd.conf
# 下2行のコメントアウトを削除
LoadModule proxy_module libexec/apache2/mod_proxy.so
LoadModule proxy_http_module libexec/apache2/mod_proxy_http.so

# 以下を追記
<VirtualHost *:80>
  DocumentRoot /var/www/html
  ServerName toofu.net
</VirtualHost>
<VirtualHost *:80>
  ProxyRequests Off
  ServerName mojiukasu.toofu.net
  ProxyPass / http://localhost:3000/
  ProxyPassReverse / http://localhost:3000/
  <Proxy *>
    Allow from all
  </Proxy>
</VirtualHost>

これでsudo service httpd startしたあとnode appすれば、http://mojiukasu.toofu.net にアクセスすることができるようになります。
Apacheのバージョンが2.4の場合は記述が異なるらしいので上記のteratailでご確認ください。

ちなみに、app.jsを常に走らせておくために、今回はpm2を使ってデーモン化しました。特に問題なく動いています。

まとめ

以上が一通りNode.js(Express) + three.jsでウェブサイト作ってみての学びです。冒頭の宣言通り半分くらいthree.js関係ない内容ですみません。ただ「一通りの流れをまとめる」ことが自分のようなレベルのビギナーの人たちには嬉しいはずだと思った、という苦しい言い訳です。色々手探りで大変ですが、どんなくだらないものでもいいのでとりあえず一通りつくって完成させてみるのが大事だなと思います。

このサイトはとひとまず作ったものの色んな所が微妙なので改修していきたい。。とりあえず見ている状態をキャプチャしてツイートに乗せたいのですがどうしたらいいのかわからず状態です。

というか、もともと文字を空中で回すつもりだったので変数名がrotateTextになってますが、途中で回さなくなったのでなんかおかしいなと思ったけどめんどくさくてそのままにしてたのも直します。

この投稿は Three.js Advent Calendar 201610日目の記事です。