WebGLで展示会のシンボルを表現した

毎年年度末くらいに学校でやってる展示会があって、その告知サイトで使うシンボルのアニメーションの実装を担当した。
すでに完成したポスターのビジュアルがあり、そのイメージを残しつつWeb向けにアレンジしたという形。

f:id:yuheiy:20151204013038p:plain

WebGLを使ったと言ってもたいして難しいことをしたというわけではなく、ライブラリを使って簡単なアニメーションをさせただけだったので実装は難しくなかった。といってもいきなりスムーズには作れてない。
まずポスターのデータを渡されたとき、シンボルがパスの集合なのを見て、SVGで書き出したデータを動かそうと考えた。でも実際にやってみると大きく2つの問題があった。

まずポスターのデータは、媒体のサイズが決まった上でデザインされているので、Webのように複数の画面サイズで見せることが想定されていなかった。
ポスターのイメージをサイトでも同じように表現するため、ウィンドウサイズいっぱいにシンボルを配置することに決めたけど、複数のサイズに対応するためにあらかじめ画像を作っておくという手は現実的ではないので、なんらかの方法でリサイズするたびに見た目を作り直さないといけない。なのでそのままのデータを使うことは無理そうだった。

それに加え、SVGではアニメーションのパフォーマンス上の問題があった。
ポスターのシンボルは1000以上のパスで構成されていて、SVGとしてパスをそれぞれ動かすと負荷が大きく、まともに見れるアニメーションにならなかった。
それで、2DのCanvas上にシンボルに似せたものを描画してアニメーションさせると、多少は改善はできた。けどまだまだスムーズとは言い難い動きだった。
このままでは処理を多少書き換えてもアニメーションが劇的に改善されるということはないと思ったので、パフォーマンスがいいらしいWebGLを使って描画することにした。

僕自身まったくWebGLの知識はなく、three.jsというのがデファクトのライブラリだということだけ知っていたので、とりあえずそれを使って2Dの描画に挑んだ。
けど、three.jsで2Dを描画する方法が初学者にとっては理解しにくく、早々と挫折した。

他に方法はないかと調べていると、2D描画をWebGLで行えるライブラリがあるということを知った。
Pixi.jsとCreateJSがWebGLでの2D描画をサポートしているらしく、使ったことのあるCreateJSを採用しようとしたけど、CreateJSにはWebGLでグラフィック描画を行う機能がないのでpixi.jsを使った。
アニメーションに関してはtween.js(CreateJSじゃない方)を使ってる。

ECC EXPO 2016 Teaser Site

結果的にかなりパフォーマンスを改善できた。
スマホに関しても、2年以内くらいに発売された機種ならまともに動いてくれる。
CanvasIE9以上、WebGLはIE11以上でないと対応していないけど、このサイト自体がIE10以下はサポートしないというポリシーなので問題なかった。まずこの前提があるおかげでいろいろ選択肢が広がったのかなという感想。

ソースこんなん

参考
HTML5 CanvasとWebGLの使い分け - ICS LAB

ポートフォリオ作り直した

Reactで作ってたポートフォリオサイトを、サーバーサイドレンダリングを有効にして作り直した。
他の部分もコードを書き直して、無駄に分かりにくくなってた処理を単純化できた。
デザインもちょっと変えて、前はナビゲーションをハンバーガーボタンにしてたのを、あんまり使いやすくないのでやめた。

ソースはGitHubで公開してる。

バックエンドはnode.jsのExpressで作って、サーバーはHeroku使ってる。
お名前.comで取得してたドメインとつなげて、前と同じURLからアクセスできるようにした。
Heroku眠ってたらなかなかつながらないし、常に起こしとこうとしても無料プランだったら18時間までしか稼働してくれないので厳しい。

Yuhei Yasuda Portfolio Site

CreateJSをbrowserifyで読み込む

CreateJSのEaselJSとかTweenJSは、node.jsのモジュールシステムに対応してないので、そのままだとrequireしてbundleすることができない。
browserify-shimを使うと解決できた。

package.jsonに以下のような記述をする。browserのところのパスは適宜調整。

{
  "devDependencies": {
    "browserify": "^12.0.1",
    "browserify-shim": "^3.8.11"
  },
  "browserify": {
    "transform": [
      "browserify-shim"
    ]
  },
  "browser": {
    "easeljs": "./src/scripts/lib/easeljs-0.8.1.min.js",
    "tweenjs": "./src/scripts/lib/tweenjs-0.6.1.min.js"
  },
  "browserify-shim": {
    "easeljs": "createjs",
    "tweenjs": "createjs"
  }
}

エントリーポイントとなるJSファイルでrequireすると、以降に読み込む外部モジュールでもcreatejsが使える。

var easeljs = require('easeljs');
var tweenjs = require('tweenjs');

console.log(createjs);

参考
browserify - jqueryと依存するプラグインをどうにかしたい - Qiita

小数を含むランダムな数値を返す関数

var random = function (min, max, floating) {
  var rand = Math.random();

  return floating || min % 1 || max || 1
    ? Math.min(min + (rand * (max - min + parseFloat('1e-' + ((rand + '').length - 1)))), max)
    : min + Math.floor(rand * (max - min + 1));
};

小数を含むランダムな数値を返すというのがなかなか難しかったので、lodashの実装を参考にして書いた。
minかmaxのいずれかが小数のときか、floatingがtrueのときには小数を、そうでないときは整数を返すようにしてる。
引数を詳しくチェックするとかの処理は省いた。

NodeListをforEachしたいときのパターン

document.querySelectorAllとかで取得したNodeListに対してループ処理をしたいということがよくある。
NodeListはArrayに似ているのでforEachとかを使いたくなるけど、それらは似て非なるものなので無理だ。
単純にforでなら回せるけどそれは嫌なので、他の方法はないかと調べると結構いろんなやり方があった。

まず単純にfor文で回すパターン。

for (var i = 0, len = NodeList.length; i < len; i++) {
  console.log(NodeList[i]);
}

for文で回すラッパー関数を作るパターン。

var each = function (NodeList, iteratee) {
  for (var i = 0, len = NodeList.length; i < len; i++) {
    iteratee(NodeList[i], index, NodeList);
  }
};

each(NodeList, function (el, i) {
  console.log(el, i);
});

forEachのthisを偽装するパターン。

Array.prototype.forEach.call(NodeList, function (node) {
  console.log(node);
});

Arrayに変換するパターン。

Array.prototype.slice.call(NodeList).forEach(function (el) {
  console.log(el);
});

NodeListのprototypeを拡張するパターン(forEachだけ)。

NodeList.prototype.forEach = Array.prototype.forEach;

NodeList.forEach(function (el) {
  console.log(el);
});

NodeListのprototypeを拡張するパターン(全部)。

Object.getOwnPropertyNames(Array.prototype).forEach(function (methodName) {
  if (methodName !== 'length') {
    NodeList.prototype[methodName] = Array.prototype[methodName];
  }
});

Object.keysを使うパターン。

Object.keys(NodeList).forEach(function (key) {
  console.log(NodeList[key]);
});

for...ofで回すパターン。
for...ofはES6の仕様で、Firefoxなら使えるけど他のブラウザはまだ対応してないので、babelをポリフィル付きで使わないといけない

for (let node of NodeList) {
  console.log(node);
}

ちなみにjQueryとlodashの実装を見てみると、どっちも内部的にforを回していた。
個人的には、prototypeを拡張するのは嫌で、sliceでArrayに変換するのも無駄なことをしているような気がするので、forで回すラッパー関数を作ってやるのがきれいだと思う。

追記(11/18)
NodeListにforループをまわすときは、Arrayに変換してからやるほうがパフォーマンスがいいらしい。
NodeListはライブオブジェクトなのでパフォーマンス的に不利、とパーフェクトJavaScriptに書いてた。
実際にchromeで試してみるとだいたい2倍くらい速かったけど、本に書いてるサンプルと処理速度が全然違って(10-100倍くらい)、今の時代そこまで気にしないでいいのかなと思った。
querySelectorとquerySelectorAllというかLive NodeListとStatic NodeList - MOL

参考
NodeList - Web API インターフェイス | MDN
Loop Over querySelectorAll Matches | CSS-Tricks

Canvasで雪を降らせた

寒くなってきたのでCanvasで雪を降らせてみた。
Snow Canvas

f:id:yuheiy:20151031154230p:plain

Canvasに苦手意識があって、それを克服したいと思ってCreate.jsに入門した。
前にCanvasを使ったのが一つ前のポートフォリオサイトを作ったときで、そのときは生のCanvasAPIをそのまま触った。
DOMのアニメーションとなかなか勝手が違ってるし、APIが扱いにくい印象を受けて、それ以来自分には向いてないので距離を置こうと諦めていた。
でも最近またCanvasを使いたい気がしてきたので、まあ使ってたら慣れてくるだろうと思ってやってみることにした。

ちなみにCreate.jsを選んだのは、これが一番有名っぽかったから。
生のAPIを触るよりも格段に扱いやすくなったけど、APIが多いから覚えるのがしんどい。
Flashっぽく書けるのが一つの利点らしいけど、僕は全くと言っていいほどFlashを触ったことがないのでその恩恵は受けられてない。
とりあえず簡単なものはパッと作れるくらいのCanvasできる人になりたい。

ポートフォリオサイトをリニューアルした(6回目)

僕は割と短いスパンでポートフォリオサイトを作り直したくなってしまう。
作ったばかりのときにはそこそこ満足しているけど、時間がたってくるとその都度なんらかの不満が生まれるので、また別のテイストでサイトを作り直す。
僕はやっぱり、サイト構成やデザインなど含めてシンプルなのがいいのだけど、前回作ったサイトはいろいろな要素が中途半端だったと気づいて嫌になってきた。
前回の制作の際には、ブログとポートフォリオを一体化してそこを自分のメインの拠点としようと考えていたけど、僕の考えとしてはポートフォリオサイトは癖のある感じにしたいし、ブログはいかにもただのブログという感じのシンプルさを保ちたいので、両方を合わせるとやりたいことは中途半端にしか表現できなかった。
なんでも足し算すればいいのではないと気づいたところで、こうしてレンタルブログを借りて、そしてまた新しくポートフォリオサイトを作り直した。

まず今回のサイトを作ろうと思い立ったとき、どのような仕組みで更新を行っていくかという点で悩んだ。
たぶん単純に考えると、CMSとしてWordPressなりを導入してそこから更新できるようにするというのが候補に上がる。 でも今回作るものに対して、WordPressのようなCMSを導入するのはかなり大げさで向いてないと思った。
毎回の更新時に入力する要素として、リード文、リリース日、担当部分、URLなどがあるのだけど、それらをWordPressでやるとなると、毎回決まったフォーマットで本文を入力していくか、大量にカスタムフィールドを追加して設定していくか、スマートなやり方で更新を行っていくことは難しそうに思った。
かといって他にちょうど良さそうなCMSも見つけられなかったので、更新するシステムの部分を自作しようかとも考えたけど、そんな管理画面自体不要なんじゃないかと気づいた。
自分が更新できればいいので、ローカルでやればいい。
ローカルで更新データをビルドしたJSの中に含めてしまえば、どこかにリクエストする手間も省けるしこれ以上速く読み込みする方法はない。
ということで、投稿データだけを含めたJSファイルを作成して、使う際はそれを読み込むようにした。
全部手入力なのが不安だけど、これくらいの規模だったら特に問題ないと思う。
投稿を追加してもbundle.jsがブラウザにキャッシュされていたら更新されないので、読み込む際に更新日のパラメータを入れてキャッシュ対策にした。

今のところは、仕組みも含めてだいたい現状の理想通りにできたと感じるので、作ったものが増えたらこのサイトを更新していくようにしたい。

Yuhei Yasuda Portfolio Site