多分「すげえウェブサイトにリニューアルしたい!」を叶えました

2015.12.22

河本です。
「すげえことをしよう」がコンセプトで「すげえウェブサイトにリニューアルしたい!」ってことで、すげえウェブサイトを作りました。
デジタルマーケティング広告企画/運用 a-works(エーワークス)株式会社

今回は技術を中心に6点説明します。
■「すげえウェブサイト」って何?
■参考にしたウェブサイト
■ThreeJSのWebGLとCSS3 3D Transforms
■pjaxでシームレスなロード
■3Dなので自然とパララックス
■ハマったところ

「すげえウェブサイト」って何?

技術的に「すげえ」って思えるウェブサイトの事かなぁと色々考えた結果、3Dでゴリゴリ動けば「すげえ」ってなるんじゃないかと思った。
ただ、ゲームみたいなウェブサイトだと、企業のウェブサイトとして成り立たせるのは難しいなとも思った。
情報を読ませるという機能を保ちつつ、「すげえ」と思わせる必要があった。
モデリングを外注すると「すげえ金額」になりそうだったので、松尾イラストを生かすプリミティブな3Dオブジェクトで構成することも何となく決まった。

参考にしたウェブサイト

ThreeJSを使ったウェブサイトで「すげえ」と思ったのはここ。
Hackery, Math & Design — Acko.net
ページをスクロールすると背景の3Dもスクロールするし、ページ内のアンカーリンクを押すとpushStateを使いつつシームレスに移動。
3D空間にあるHTMLをカメラが追いかけている感じ。

3Dを使ったウェブサイトにありがちな、新しいUIとインタラクションを強制させるのはかっこ悪いと思っていたので、このウェブサイトを見習って3D空間にHTMLを配置することにした。

ThreeJSのWebGLとCSS3 3D Transforms

背景は全画面canvasにしてTHREE.WebGLRendererで描画している。

3D空間内の単位を定義しないと、後々世界観が破綻することになるので、最初に単位を決めた。
定番なのは1[unit]=1[mm]や1[unit]=1[m]だと思う。
キャラクターの実寸が未定義のため、1[unit]=1[px]にした。
それに合わせて、ニアクリップ平面にある100[unit]x100[unit]の絵は100[px]x100[px]として表示されるようにした。
幅合わせのデザインのために、画角はfovyではなく、fovxで指定した。

地面は惑星(というかTHREE.SphereGeometry)にテクスチャを貼付けただけ。
キャラクターや絵を板ポリに貼って立てている。
座標系の親子関係はこんな感じにして、coreを回転させることで惑星上に配置している。
world <- core:THREE.Object3D <- base:THREE.Object3D <- mesh:THREE.Mesh
キャラクターのアニメーションはbaseを動かしている。

手前のDOM要素はTHREE.CSS3DRendererでニアクリップ平面に配置している。
THREE.SceneはTHREE.WebGLRenderer用とTHREE.CSS3DRenderer用で別々に定義している。
THREE.WebGLRenderer側で構築した座標系をTHREE.Object3D.getWorldPositionとTHREE.Object3D.getWorldQuaternionを使ってコピーしている。

スクロール時のカメラワークは、スクロール位置から3D上のカメラ位置を計算し直し、3D上のDOM要素をカメラで追いかけている。
後述のpjaxで#Main要素を読み込むが、読み込み完了時に同じ高さのダミー要素を配置し、同じ量スクロールできるようにしている。

各ページごとにグローバルなpositionとrotationを定義し、ページ遷移時にカメラを動かすことでページ遷移を表現した。
遷移時のカメラのpositionはTHREE.CubicBezierCurve3で補間し、rotationはTHREE.Quaternion.slerpで補間した。

THREE.CSS3DRendererはtransform-style:preserve-3dを使っているのでIE11以下(つまりすべてのIE)でうまく動かなかった。Edgeからはサポート。
また、Chromeで拡大率が100%以外のときもうまく表示できない。(原因は不明)

そのため、preserve-3d未対応または拡大率が100%以外の時は、THREE.CSS3DRendererを使わず、通常のDOM要素のままにしている。
ユーザーの学習コストの他にも、ウェブサイトとしての保守性の面でもそのままのHTMLを表示しているメリットはあると思う。

WebGLをサポートしていないブラウザやスマホの場合は思い切って背景画像にしているが、違和感無く見れる。

今後スマホのスペックが上がって行けば、スマホでもWebGL版を表示するかもしれない。
(一応レスポンシブにしているので、PCのブラウザで横幅を狭くしてあげると簡単に確認できる。)

pjaxでシームレスなロード

#Main要素をpjaxで切り替える事にした。
defunkt/jquery-pjax

共通のヘッダとフッタはHTMLテンプレートエンジンで差し込んでいるのでコーディング上の不都合も無い。
#Main要素をフェードしつつカメラが移動する。

3Dなので自然とパララックス

パララックススクロールは、手前にある物より奥にある物をゆっくり動かすことで2Dで3Dのような奥行き感を感じさせる手法で、スーパーマリオワールドなどのスーパーファミコンのゲームでもよく使われていた。

例としてThreeJSでパララックススクロールを作ってみた。(テクスチャは松尾さん)

See the Pen threejs parallax by Yusuke Kawamoto (@novogrammer) on CodePen.

たまにそれを理解せずに、重なり順で奥にあるはずの物を速く動かしたり、逆方向に動かしたりする、不自然なパララックス表現のウェブサイトがあり、ストレスを感じていた。

今回は3Dを使っているので自然とパララックス表現の効果があり、満足している。
パララックス表現という点では、THREE.CSS3DRendererを使った、こちらの手法が主流になるべきだ、とさえ思っている。

ハマったところ

ThreeJSを案件で使うのが初めてだったので、基本的にシェーダーは使わない事にした。
唯一使っているのは透明テクスチャの影のためにTHREE.ShaderMaterialをTHREE.Object3D.customDepthMaterialへ設定している箇所。
(実際にはTHREE.Object3Dには定義されておらず、THREE.WebGLShadowMapから参照しているのみ)

THREE.Object3D.customDepthMaterialを使っている例は
three.js / examples / animation / cloth

しか見つけられなかったので、シェーダーを丸々コピー。

影が微妙にズレるため、もしやと思いシェーダーを読んでみるとvertesShaderに
vUV = 0.75 * uv;
という行があったので
vUV = uv;
にしたら直った。

 

THREE.CSS3DRendererに共通かもしれないが、widthが奇数の場合は半ピクセルずれるため、THREE.Camera.position.xに0.5足してあげる必要があった。

 

前述のTHREE.CSS3DRendererとtransform-style:preserve-3dの件も結構ハマった。

制作クレジット

デジタルマーケティング広告企画/運用 a-works(エーワークス)株式会社

Client: a-works株式会社

Planner: 花岡洋一(人間)、山根シボル(人間)
Director: 花岡洋一(人間)
Copy Writer: 久岡崇裕(parks)
Technical Director: 河本裕介(人間)
Designer: 山根シボル(人間)、松尾聡(人間)
Front-end Engineer: 河本裕介(人間)
Photographer: 島田洋平