弊社 dot by dot inc. のコーポレートサイトをリニューアルしたときのいろいろをメモ。見た目シンプルなわりにいろいろあったのでね
どんな仕組みでつくる?
日々増えるニュースを簡単に更新できるようなブログ形式、前バージョンの背景エフェクトもってくる(かつページ遷移においてリロードしない=エフェクトを途切れさせない)という前提で検討した結果、WordPress を CMS に使いつつ WP REST API で JSON ひっぱってきて React でごにょごにょやるんがいーかなーと。
React
ちょうどどうやってつくろっかなーと考えてる時に、VirtualDom - なぜ仮想DOMという概念が俺達の魂を震えさせるのか - Qiita を読んで、どうやらこれ流行りそう今っぽいーということでフロントのフレームワークを React に決定。ちょうど IntelliJ IDEA 14 が JSX の syntax をサポートしたってのもある。(わたし IDE のコードアシストないと不安なひと)
React いわゆる View だけっぽいフレームワークでシンプルなので、公式のチュートリアルやればだいたい理解できる。ぎゃくにいうとそれ以外の部分は自分でがんばらないといけない。ま、WordPress から JSON でデータひっぱってくるだけなのでそんなに複雑なことはないけど。
一人React.js Advent Calendar 2014 もよくまとまっててわかりやすい。
JSX
React の Component を書くのはふつうの JS でだらだら書くのでもできるんだけど、やっぱ JSX で書いたほうが効率いいし見通しもいい。で、これまで HTML5 系のサイトをいくつか作ったときは CoffeeScript だったので coffee-react つかってみよーと思ったものの、IDEA が cjsx の syntax 理解してくれなかったので plain な JSX で書いた。括弧が多いとか行末コロン忘れがちとかいろいろあるものの、JSX は JavaScript にコンパイルするときに ECMAScript 6 の文法(全部じゃないけど)が使えるオプションがあって、ES6 で書けば素の JavaScript で書くよりはストレスすくない。arrow とか template strings とか destructuring らへんは特に CoffeeScript っぽくて使いやすい。
React Router
React Router は URL に応じて React の Component を自動的にとっかえてくれる Component。URL と Component の対応付けも JSX で宣言的にかける、わかりやすい。Express の Router みたくパラメータ受け取ったりもできる。Express ほど柔軟じゃないけど。
同一ドメイン内のリンクは Link コンポーネントか Navigation mixin の transitionTo メソッドつかう。デフォルトだと昔 Flash で Progression でやってたみたいな URL hash で URL 書き換えるんだけど、オプションつけると HTML5 の History API つかってくれるようになる。
今回、英語版の URL を mqTranslate のデフォルト設定、/en/ を前につけるっていうのを採用したんだけど、そこのルーティングが現状の React Router ではいい感じに書けなくて、ちょっと冗長になってるのが残念。ま、動いてるので OK。
/tag/hogehoge とか /page/123 とか、WordPress 側ではちゃんとページでるやつが React 側で未実装で Not found になってるやつがあるので、どっかのタイミングで実装しないといけないかな・・
Transition
別ページへのトランジション、特にメンバー一覧から詳細への動きをシームレスにしたかったんだけど、タイムアウトにより断念。いちおう React にはそのへんできそうな仕組みあるので次回やるときもうちょい研究する。かも。
Google Analytics
React Router が Component 切り替えるタイミングでふつうに GA の API を呼ぶ。以上。
WordPress
REST API
WordPress のデータを JSON で取得できるようにするプラグイン、WP REST API を導入。以前、螢光 TOKYO のサイトをつくったときは JSON API のほうを使ってたんだけど、もうメンテされてないのと WP REST API は WordPress 本体に組み込まれそうなのでこっち採用。使い勝手は JSON API のほうがよい気はするけど。
React からはページを表示する Component が body に append されたタイミング、componentDidMount が呼ばれたタイミングで API をコール。API からデータ返ってきたときには Component が削除されてる可能性があるから isMounted() でチェックしろってどっかで読んだけどいまコード見たらチェックしてないなー
i18n
WordPress に mqTranslate をいれて普通に多言語対応。REST API よぶときに現在の言語をオプションにつけて ?lang=en とかして呼べば指定した言語のデータが返ってくる。現在の言語を取得するために自分で mixin 書いてみたんだけど、はたしてこれが正しいやりかたなのかは不明
Advanced Custom Field
WORK の CREDIT ぶぶんとかは、フリーで入力するとレイアウトがばらばらになったりするので、Advanced Custom Field プラグインをつかって専用のカスタムフィールドに入力するようにしてる。カスタムフィールドに入れたデータはそのままでは REST API で返ってこないので、返してくれるようにする別のプラグインをさらに追加。
SEO
JS で JSON よんできてクライアントサイドでページつくる仕組みの WordPress テーマなので、ふつうにそのまま検索エンジンに同じのを出すとうまくインデックスされないかも?(Google は JS 実行してくれるっていう話ですけども)なので、検索エンジン向けにはふつうの WordPress のテンプレートで出力するようにする。Multi Device Switcher っていうプラグインをいれて、検索エンジンであろう User agent に対してだけふつうのテンプレート(Twenty Fifteen)を出すようにすればできあがり。React 使ってるんだったら Server side rendering の仕組みをつかってちゃんとユーザーが見てるのと同じ HTML を返すのがただしいのかなー、WordPress と共存させるのがむずそうだけど。。
Dot Pattern
背景のドットパターン(前景?)は前バージョンのものをベースに表示部分を最適化した。前バージョンは SVG で全部描画してたのだけどシンプルな見た目のわりに CPU 100% 使い切ったりですげえ重かったのを、WebGL (three.js) で書きなおした。WebGL 対応してない環境では SVG にフォールバックしてレンダリング。レンダラーを切り替えられるように、ドットの配置・動きを計算する部分とレンダリング部分をちゃんとわけた。
Transition between patterns
各ドットパターン間のトランジションは、それぞれのドットが一番近いドットに移動するようにしてある。一定の距離よりも近い場所になければ消滅したり発生したり。いちばん近いドットを探すのは、総当りでやっちゃうと重いので quad tree で。実際のコードはこのへん。JS の quad tree 実装はいろいろあるんだけど、いくつか試した感じだとこれが今回の用途には使いやすかった。
WebGL
円を描くだけなのでいつも使ってる three.js じゃなくて、べつの 2D に特化してるやつにしてみようと two.js を試してみたら意外とすごく重くて(この Example、100 個しか円描いてないのにめちゃ重、、)、結局 three.js でごりごり書くことに。
ふつうに WebGL できれいな円を描こうとすると、ポリゴン数がたいへんなことになっちゃう。
これは 32 ポリゴン、大きいドット描くときはもっとポリゴン数おおくしないとカクカクしてるのがバレる。ので、ポリゴンで描くんじゃなくて fragment shader で描くことにした。fragment shader 内で四角ポリゴン中心からの距離を計算して、一定距離以下だけ描く、それいがいは discard する。そのままだとジャギるので antialias かける。antialias ちゅーか、境界をぼかすんだけど。そのへんはこの記事を参考に。実際は四角ポリゴンだと discard する領域が多くて重くなるので 8 角形にしてる。
three.js の実装てきには、あらかじめ 1024 個のドット(最大でそんぐらいでる)を描くのに必要なバッファを用意しておいて、描くタイミングで座標全部つめこんで drawcall 1 回で全部描く。CPU 側の処理はドットの位置計算だけになったのでかなり軽くなったけど、シェーダー負荷があがったので GPU がしょぼいと重いかも、、、?
SVG
SVG renderer のほうはすごくシンプル。すべてのドットには個別の id がふってあるので、未知の id が追加されたら SVG 側にもcircle 要素追加、すでに追加されてたら移動、なくなってたら削除。これらの操作には前バージョンとおなじく Snap.svg を利用、らくちん。
Web Font
本文のフォントには Noto Sans Japanese を使ってる。自分で Web font 化しないといけないかなーと思いつつぐぐったら Google Fonts が Early Access として Noto Sans Japanese を用意してくれてたのでそれを採用。
Favicon
メニューの現在地をしめすドットのカラーは背景パターンの色と同期することにしてたので、じゃあ、favicon も一緒に変えてみよーということで実装。favicon を指定している link タグの href 属性を、canvas の toDataURL() で書き換えればいいだけ。コードこのへん。ただしブラウザによってできるできないがあって、Chrome は 2-3 fps ぐらいだけどちゃんとアニメする、Firefox は fps はやすぎるとついてけなくてチラチラするけど表示はできる、Safari はアニメ不可 & そもそも設定できたりできなかったり。
Google Maps
Google Maps にはっつけるアイコンも favicon みたく背景同期したかったので、SVG アイコンとして実装。SVG アイコンのみ動的にカラーが変更できるっぽい。指定カラーがパス全体に反映されちゃうので、当初のデザインが再現できなかったんだけどじゃがーさん OK でたのでよし。
Build System
上記さまざまな仕組みは、いつもどおり Grunt + Browserify でビルド。React の JSX も reactify かませば普通に require できる。本番サーバーへの deploy も grunt-rsync で。
公開してから指摘されて気づいたんだけど、browserify のデフォルトだと? require したモジュールの絶対パスがそのままソースに含まれちゃうので、browserifyOptions の fullPaths を false にしといたほうがよさげ。ファイルサイズもちょっと小さくできる。
Conclusion
WordPress + REST API + React (+ Server side rendering) って組み合わせはもっと流行ってもいいんじゃないかなー。重くなりがちな WordPress も JSON ひっぱってきてクライアントサイドで画面つくればけっこういい感じに軽いサイト作れる。今回のソース一式公開しておいたのでだれかもっと汎用的な仕組みつくってくんろ。僕のブログもこの仕組でリニューアルしたい。
で、
最後までちゃんと読んで、ふんふん、おもろい、なんかわからんけど、おもろい、わたしもこんな感じで仕事してみたい! と思った、そこの 20 代のあなた。わたくし Saqoosha は dot by dot inc. にてちょうど弟子を募集していますので、いますぐ GitHub のアカウントを書いて [email protected] までご連絡を。わたしがこういうかんじでモノ作ってるのを隣で観察することができますよー