制作事例

Published on 2017/1/8

Naokazu Terada

制作事例

2017/1/8

Naokazu Terada

この記事では、2016年11月に公開された「森のDNA図鑑 仁保の森 2016」というウェブサイトの制作過程の話をしています。

森のDNA図鑑 仁保の森 2016


前回の記事で、サイトの目的や背景、当日の撮影など素材準備までの話をしました。今回は、技術的な話を中心にしたいと思います。

前回の記事 → 森のDNA図鑑のできるまで 1/2

Dependencies

まず、Gemfileとpackage.jsonを晒します。各ツールやライブラリに言及しながら、どうやって作ったか具体的なコードも交えながら書いていきます。

Gemfile

source 'https://rubygems.org'

# For faster file watcher updates on Windows:
gem 'wdm', '~> 0.1.0', platforms: [:mswin, :mingw]

# Windows does not come with time zone data
gem 'tzinfo-data', platforms: [:mswin, :mingw, :jruby]

# Middleman Gems
gem 'middleman', '>= 4.0.0'
gem 'middleman-livereload'
gem 'middleman-deploy', github: 'middleman-contrib/middleman-deploy', branch: 'master'

package.json

{
"name": "dna-of-forests",
...
"devDependencies": {
"babel-core": "^6.17.0",
"babel-loader": "^6.2.5",
"babel-polyfill": "^6.16.0",
"babel-preset-es2015": "^6.16.0",
"clean-webpack-plugin": "^0.1.13",
"css-loader": "^0.25.0",
"node-sass": "^3.10.1",
"pug": "^2.0.0-beta6",
"sass-loader": "^4.0.2",
"vue-loader": "^9.7.0",
"webpack": "^1.13.2"
},
"dependencies": {
"flickity": "^2.0.5",
"js-cookie": "^2.1.3",
"json-loader": "^0.5.4",
"lodash": "^4.17.0",
"mobile-detect": "^1.3.3",
"three-js": "^79.0.0",
"vue": "^2.0.3",
"vue-router": "^2.0.1"
}
}

Middleman

KARAPPOでは、静的サイトジェネレーターの Middlemanを良く使います。4.x系からはExternal Pipeline という仕様が採用され、npmの恩恵を享受できるようになりました。今回SPA1な作りでいけそうだったため使いませんでしたが、YAMLで書ける Data Files は本当に重宝しています。YAMLはJSONよりシンプルで、コメントも書けるし、データ・フォーマットの中では一番好きです。

GithubにMiddlemanプロジェクトのテンプレート的なのを公開してるので、興味ある人は参考にして下さい2。

Vue.js

Vue.jsは、UIパーツの構造やスタイル、機能をコンポーネント化して管理(したり他にも色々)できるフレームワークです。今回初めて導入しました。ずっと気になっていたのですが、VirtualDOMを採用した2.x系がちょうど10月に正式リリースされるというナイスなタイミングでした。Riot.jsとも迷いましたが、他のフレームワークとの比較という記事で言及されているトランジッションAPIの有無とルーター機能の優位性が、今回の案件では効いてきそうだと思ったので導入を決めました 3 。

コンポーネントと独自フォーマット

Vue.jsやRiot.js、React などに共通する概念に、コンポーネント化があります。そのうち、Web Components に集約されていく気もしますが、今のところPolyfillなしでは使えないので、ライブラリ毎に独自フォーマットが定義される結果になっている状況です。

ReactにはJSXという独自フォーマットがあり、Javascriptを拡張してPHPのようにコード内に構造(HTML)を記述します。Riot.jsはWeb Components-Likeを謳っていて、HTML内のscriptタグにコードを埋め込むようです(スタイルは別途定義なのかは、実際に使ってないので不明)。これに対してVue.jsのSingle File Components(.vue)は、構造(template)とスタイル(style)とコード(script)を1つのファイルにまとめて書ける独自フォーマットで、個人的にはこれがかなり筋がいい気がしています。

例えば、音関係はボタンそのものとaudio要素、ホバー時のスタイルにcookieやウィンドウのフォーカスが外れた時の処理など、全てsound-button内に閉じ込めることができてスッキリしました4。

sound-button.vue


a(v-on:click="click") div span SOUND audio(loop) source(src='/dna-of-forests/sound/forest.mp3' type='audio/mp3') &nbsp; <style lang="sass?indentedSyntax" scoped> a<br /> >div<br /> height: 100%<br /> background-position: center<br /> background-repeat: no-repeat<br /> background-image: url(/dna-of-forests/img/gnav/sound-on@2x.png)<br /> background-size: 18px 15px<br /> transition-duration: 0.2s<br /> opacity: 0.5<br /> >span<br /> display: none<br /> &:hover<br /> >div<br /> opacity: 0.9<br /> &.off<br /> >div<br /> background-image: url(/dna-of-forests/img/gnav/sound-off@2x.png)<br /> background-size: 17px 15px<br /> </style> <script> import Vue from 'vue'; import MobileDetect from 'mobile-detect'; import Cookies from 'js-cookie'; export default Vue.extend({ mounted: function() { var md = new MobileDetect(window.navigator.userAgent); this.sound = this.$el.querySelector('audio'); this.sound.volume = 0.3; if(md.mobile()){ // モバイルのときだけ、ウィンドウが背面にいくとサウンド停止(戻ってくると復帰) window.addEventListener('blur', ()=> { if(!this.sound.paused){ this.pauseByWindowBlur = true; this.toggleSound(false); } else{ this.pauseByWindowBlur = false; } }, false); window.addEventListener('focus', ()=> { if(this.pauseByWindowBlur){ this.toggleSound(true); } }, false); } this.toggleSound(!(md.mobile() || parseInt(Cookies.get('isSoundOn'),10)==0)); }, methods: { click(){ this.toggleSound(this.$el.className === 'off'); }, toggleSound(_on){ if(_on){ this.sound.play(); Cookies.set('isSoundOn', 1); this.$el.className = ''; } else { this.sound.pause(); Cookies.set('isSoundOn', 0); this.$el.className = 'off'; } } }, data: function(){ return { pauseByWindowBlur: false }; } }); </script>

そして、こいつを使うときには


てな感じで書きます。

JSXと違って、同じファイル内でtemplate, style, scriptの3つのスコープがしっかり区切られて可読性が高いです。また、属性にlang="pug"と書けば、生のHTMLの代わりにPug(旧Jade)形式で記述することもできて、そこもJSXより好きなところです。注意点は、sassを使いたい場合はlang="sass?indentedSyntax"と書かないといけないところです。単にlang="sass"だと、scssで解釈されます。

style要素にscoped属性をつけると、レンダリングされた時にインスタンス毎にハッシュクラスを作って紐付けてくれるので、styleの影響範囲をモジュール内に収められるのも良かったです。

DNAカラーバーコードの描画

各サンプルのDNA配列をカラーバーにして表示する部分は、パノラマページ、リストページ、ドロワー部分でそれぞれ微妙に違う表現になっていますが、これらは、A・T・G・C5の並んだ文字列を与えるとcanvasにバーコードとして描画するというシンプルなコンポーネントを使って実装しています。パノラマでは、描画したものをBase64にしてbackground-imageに指定し、cssのkeyframeとbackground-positionでアニメーションさせています。

vue-router

vue-routerという本家のルーターがあるのも良かった点です6。ルートにはパラメータを含めることもできるので、マーカー毎にindexを振って固有のURLを発行することができました。ルート設定の部分を下に貼っておきます。panorama、list、aboutなど、ページ毎にコンポーネント化し、topページはpanoramaページにmodalがかぶさるだけなので、同じインスタンスを共有するということもvue-routerの設定でできました。

const router = new VueRouter({
routes: [
{ path: '/', component: require('./components/page/panorama.vue') },
{ path: '/panorama', component: require('./components/page/panorama.vue') },
{ path: '/panorama/:index', component: require('./components/page/panorama.vue') },
{ path: '/list', component: require('./components/page/list.vue') },
{ path: '/list/:index', component: require('./components/page/list.vue') },
{ path: '/about', component: require('./components/page/about.vue') }
]
})

また、ドロワーが開いている状態で、リスト/パノラマを切替ても同じマーカーIDが引き継がれるようにして、URL的にも辻褄が合うようにしたのですが、このあたりは、Vue.jsのリアクティブシステムが大活躍です。こういうのをjQueryで実装するとコードが汚くなって萎えます…。

http://special.ycam.jp/dna-of-forests/#/list/s-15

上の状態で画面左のグローバルナビからパノラマビューに切り替えると、下のようにパノラマビューの方で15のマメヅタが選択された状態に遷移します。

http://special.ycam.jp/dna-of-forests/#/panorama/s-15

具体的には、こんなコードで実装しました。該当部分のみ抜粋です。

global-nav.vue


nav router-link.top(to='/' v-bind:exact="true") div span TOP router-link.panorama(v-bind:to="linkUrl('/panorama')") div span 360 router-link.list(v-bind:to="linkUrl('/list')") div span LIST router-link.about(to='/about') div span ABOUT   <style lang="sass?indentedSyntax" scoped> /* 省略 */<br /> </style> <script> import Vue from 'vue'; // 登録 Vue.component('sound-button', require('./sound-button.vue')); Vue.component('lang-button', require('./lang-button.vue')); export default Vue.extend({ methods: { linkUrl(default_path) { if(this.$route.params.index){ return default_path+'/'+this.$route.params.index; } else { return default_path; } } } }); </script>

Three.js

WebGLを使った表現にはThree.jsを使っています。THREE.SphereGeometryにtextureとしてvideo要素の内容を貼っています。動画は2秒間の動画をループ再生しています。3840x1920pxの4K動画なので、あまり長尺だとデータサイズ的に流石に厳しかったためです。動画は、時間をずらした2つの動画をアルファをつけてフェードイン・フェードアウトさせて重ね、継ぎ目に違和感が出ないようにしました。

マーカーの配置

マーカーの座標は、先の記事で紹介したYCAMの方に撮ってもらった360°パノラマ動画と見比べながら、手作業で決めていきました。まず緯度経度(latitude, longtitude)を合わせ、その後カメラからの距離(radius)を目視で設定していきました。Sphereの大きさやマーカーの大きさなどをできるだけ実際のサイズに合わせて、直感的に捉えられるようにしながら、なんとかやりました…。工夫した点としては、下記のようなコードでガイドラインを引いて少しでも調整しやすくしたことでしょうか。

// 10°区切りのメッシュグリッド
var sphere = new THREE.Mesh(
new THREE.SphereGeometry( 500, 36, 18 ),
new THREE.MeshPhongMaterial({
color: 0xffffff,
wireframe: true
})
);
this.scene.add(sphere);

// 3軸ガイド
var axis = new THREE.AxisHelper(300);
axis.position.set(0,-10,0); // 原点だとカメラと同じ視点になるので表示されないので少しずらす
this.scene.add(axis);

そして、できた設定値を、今度は空間内に実際にマーカーを配置するために、XYZの座標に変換する必要があったので、下記のようなコードで変換します。

// 緯度経度とカメラからの距離を空間座標に変換
geoCoordsToVector(latitude, longitude, radius) {

// 仰角
var phi = (latitude) * Math.PI / 180;
// 方位角
var theta = (longitude - 180) * Math.PI / 180;

var x = -(radius) * Math.cos(phi) * Math.cos(theta);
var y = (radius) * Math.sin(phi);
var z = (radius) * Math.cos(phi) * Math.sin(theta);

return new THREE.Vector3(x, y, z);
}

このように、サンプル・マーカーは内部的には3D空間内の座標を持っていますが、実際に描画されているマーカーはHTML要素で、canvasの上に重ねています。描画クオリティを考慮の上、この仕様にしました。

描画更新の度に、下記の関数を使って平面上の位置を計算し、アンカー位置を更新しています。

// canvas平面上の位置に変換
verctorToXY(vector) {

var vector = vector.clone();
var widthHalf = 0.5*this.renderer.context.canvas.width;
var heightHalf = 0.5*this.renderer.context.canvas.height;

// 対角線上の位置も返してしまわないように判定
if(this.frustum.containsPoint(vector)) {

// Within camera
vector.project(this.camera);

vector.x = ( vector.x * widthHalf ) + widthHalf;
vector.y = - ( vector.y * heightHalf ) + heightHalf;

return {
x: vector.x/window.devicePixelRatio,
y: vector.y/window.devicePixelRatio
};
}
return null;
}

ポイントとしては、9行目でカメラ視野に入っているかどうかを判定している部分でしょうか。ここで、視野内にあるものだけ座標を返すようにしました。

以上、「森のDNA図鑑 仁保の森 2016」の制作過程を色々寄り道しながらまとめてみました。SPAや3Dなど、普段あまり使わないフォーマットで技術的なトライも出来たし、面白いサイトになったのではないかと思っています。この記事の内容が、少しでも誰かのお役に立てば嬉しいです。

フィードバックなどがあれば、@naokazu_terada までお願いします。 Please contact me (@naokazu_terada) if you have a question or feedback.

追記: ソースコードをGithubに公開しました。興味のある人は御覧ください。

  1. Single Page Application
  2. ちなみにMiddlemanにはProject Templates というのも用意されてるんですが、Middlemanをグローバルにインストールしたくないので使ってません。いつもこのrepositoryをcloneするところから始めてます(アナログ…)。
  3. 今読み返すとこの記事、Riot.js側のアップデートにともなってか、更新されていました。こうしたドキュメントの充実度もVeu.jsの凄さですね。プロダクトオーナーのEvan YouさんがPatreonで支持を集めてフルタイムコミッターになるなど、今熱いプロダクトです。
  4. コンポーネントという概念がなければ、HTMLの構造はHTMLファイルに、スタイルシートはCSSファイルに、コードはJavascriptファイルに、とバラバラに書かなくてはならず、自分で書いたものでもパッと該当箇所を探し当てるのが難しかったりします。
  5. DNAを構成する4つの塩基
  6. Reactだと何を使えばよいのか迷った経験があります。
制作事例

Published on 2016/12/27

Naokazu Terada

制作事例

2016/12/27

Naokazu Terada

山口情報芸術センター [YCAM] の「森のDNA図鑑 仁保の森 2016」というウェブサイトを制作、2016年11月に公開させていただきました。今回はこのサイトの制作過程の話をしてみようと思います。これまで、こういった制作の裏側を記事にしたことはないので、使っているツールなどにも触れてみます。記事は2回にわけ、前篇はこのサイトの背景から当日の撮影まで、後篇は技術系の話を中心にまとめる予定です。

森のDNA図鑑 仁保の森 2016

背景

まず、このサイトは何なのか?という話です。ご存知ない方のために一応、依頼元であるYCAMのご紹介からします。

YCAMについて

山口情報芸術センター [YCAM] というメディア・アートの複合施設が山口県山口市にあります。YCAMができたのは2003年。実はこのあたりは私の地元で、生まれ育った馴染みの場所です。元々は市民球場や公園、県立高校などがあり、私も幼い頃は缶蹴りなどして遊んだのどかな田舎町でした。そんな所に、国内有数の最先端でエッジの効いた施設が建造されて驚いたのも、もう10年以上前のことになります。以降、アート作品の展示だけでなく、内部研究開発チームと外部アーティストとの共同作品制作や、映画や演劇等の公演、全国から人が集まる大きなイベント開催など、実に様々な活動を展開されています。

そんなYCAMが始めた一番新しい取り組みのひとつにYCAMバイオ・リサーチがあります。

YCAMバイオ・リサーチについて

YCAMバイオ・リサーチは、“バイオテクノロジーの応用の可能性を多様な切り口で模索するプロジェクト”です。近年の急激な価格低下によって、個人でも活用できるレベルになったこの分野で、セルフビルドでゼロから館内にバイオラボを設置したり、一般参加者への啓蒙活動など、活発に活動を続けています。

たまたまですが、私の父(元理科教師)も、バイオ・リサーチのイベントに参加するようになりました。これまでのアート/テクノロジー文脈ではなく、サイエンス的なテーマだったことで親しみやすかった様です。設立から10年を超えたYCAMが、このように地域の色んな分野の人々を巻き込みながら活動の幅を拡大していっているのも、新らたなフェーズに入った感があり、興味深いです。

依頼内容と発想

今回、YCAMバイオ・リサーチの方からというワークショップの参加者が、森の中で採取してきた植物のDNAの解析結果をまとめて見られる「ウェブ図鑑」を作りたい、という依頼がありました。このワークショップ「森のDNA」の主たる目的は、「DNAバーコーディング1」という技術を使った種の同定作業の実践とアーカイヴ(「森のDNA図鑑」の制作)でした。

今回の依頼内容を聞いてパッとイメージしたのは、360°パノラマ映像の中にマーカーが浮いていて、クリックすると詳細が見られるというアイデアでした。採取場所の状況・情報をより多く残すことができて、参加できなかった人にも現地の臨場感を伝えることができるので、興味を持ってもらうことができそうで、今回の企画にピッタリなのではないかと思いました。

余談ですが、KARAPPOは以前にも「ウェブ図鑑」を作ったことがありました。「マジカル動物図鑑」というメディア・アートイベント参加者の描いた塗り絵をウェブ上にアーカイヴしたサイトで、1000以上のアイテムが収録されています。このときは、塗り絵自体が魅力的だったこともあり、一覧性と次々に見ていく楽しさを追求しました。しかし、今回は同じウェブ図鑑ですが、収録数よりも試み自体の面白さが見た人に伝わるように、別の見せ方にする方がしっくりくる気がしたのです。

準備から当日の撮影まで

ミーティングや連絡ツール

KARAPPOは千葉にあるので、ミーティングは基本Google Hangoutでした。ハングアウトは、相手もGoogle Calendarを使っていれば、予定を追加して編集画面から「ゲストを追加」しておいて、カレンダーから直にハングアウトのチャットルームに入れます。いちいちルームを作って、直前に別のメッセージツールでURLを共有して…という作業が必要なくて直感的で便利です2。Google DocsのSpreadsheetも、諸々のデータの整理や受け渡しなどでかなり重宝しました。Facebookメッセージも少人数の簡単なやり取りに使いました。メール以外にチャット形式のツールがある方が便利ですよね。

ロケハンと撮影機材

バイオリサーチの方に現地でロケハンしてもらい、できるだけ広く見渡せて、画面内にバランスよくサンプルがばらけそうな場所を、検証してもらいました。ロケハンで事前に撮影した動画や写真を使ってデモを作り、ハングアウトで話し合いながら進めていきました。

カメラはKodakのPIXPRO SP360 4Kを2つ繋げて使用しました(写真)。RICOHのTHETA Sも試して、画像のつなぎ目(2カメラ間の視差)はTHETA Sの方がキレイでよかったのですが、森の映像だとつなぎ目はほとんど気にならなかったので、より高解像度のPIXPROを選びました。人工物などの直線が映り込むような場面では別の選択をしていたかもしれません。

pixpro

なお、撮影や機材調達などは全てYCAM側で行っていただきました。スタッフの方が機材の扱いに長けているため、やりとりが非常にスムーズでした。

また、今回感心したのが、バイオ・リサーチの記録のとり方でした。過去の活動の記録も、写真や動画の形でアーカイブされ公開されています。フィールドワークの様子は、ヘッドマウント型のカメラを使って記録に残されていて、あとから追体験できたのも、リモートにいながら現場の状況や情報共有にとても役立ちました。KARAPPOでも今後の参考にさせてもらおうと思いました。

当日の撮影

当日は、撮影するときのカメラ位置が分かりやすいように蛍光ピンクの紙を被せて、参加者にはそれが見える範囲で採取するようにしてもらいました(写真:1)。参加者は辺りを探索し、採取する植物が決まったら、配られたiPodTouchで近景を撮影(写真:2)、サンプルを採取、そして最後に採取場所に丸いサンプル・マーカーを置いてもらいます(写真:3)。全てのグループの採取が終わった後で、サンプル・マーカーをひとつづつグループ名を読み上げながら回収していく様子を本番のカメラ位置から動画撮影してもらいました。あとで、この動画と本番用動画を見比べながら、3D空間内にマーカーを配置していきます。


撮影:山中慎太郎(Qsyum!) 写真提供:山口情報芸術センター[YCAM]

ここまでは当日までのお話でした。次回は、この撮影でとれた動画や写真などの素材を元に、実際のウェブができあがるまでの制作過程を、技術的な話を中心にしたいと思います。

続き → 森のDNA図鑑のできるまで 2/2

  1. これまで、見た目などに頼るしかなかった種の同定が、インターネット上に公開されたDNA情報のデータベースと照合することで、DNA情報さえ取り出せれば、非専門家でもかなりの精度で種の同定が可能になる技術のこと。
  2. KARAPPOではG Suiteを使っていて、分かりやすいURIのハングアウトルームを作ることもできるのですが、この方法だと別ドメインのユーザーは同じルームに入るのに毎回承認が必要でこれがなんとも不便なので、相手がカレンダーを使っていれば、このやり方の方が好みです。