はじめまして。Japanリージョンゲーム事業本部でフロントエンジニアをしている@hazumuです。
現在はこちらの記事でも取り上げられているQuizNow(クイズナウ)のブラウザ版やクイズの投稿画面の開発を担当しています。
アニメーションやデザインアプリを効率よく開発するには~RabbitCam/QuizNowの事例から~ — Mobage Developers Blog
今回はQuizNowというiOSアプリを、Webブラウザ上で動くシングルページアプリケーションとして実装し直した際の技術選択と、ワークフローについてお話ししたいと思います。
これからHTML5でインタラクティブなシングルページアプリケーションの実装を始める方や、iOSアプリをWebに向けて展開しようと考えている方の参考になれば幸いです。
QuizNowとは
毎回このブログを読んでくださっている方には、再びの紹介になってしまいますが、まずは今回の掲題のQuizNowについて紹介します。
QuizNowは好きなトピックで友人とリアルタイムに勝負できる対戦クイズアプリです。
対戦の様子はこちらの動画をご覧ください。
ブラウザ版を展開するに至った経緯
ご覧になられた通りQuizNowはiOSのアプリです。今回このアプリをブラウザ版を開発することになった経緯は以下のものが挙げられます。
Android,iOS6ユーザー向け
ソーシャルメディアからの流入の容易さ
リッチなユーザー体験を届けるための技術的挑戦
アプリの開発であれば、Android、iOSの異なるプラットフォームに向けて異なる言語で実装が必要な上、同じOS間でもバージョンの差異によって異なる実装をする必要が出てきます。その点ではアプリをブラウザ上で動くようにHTML+CSS+JavaScriptで実装した場合、プラットフォームにあまり依存することなくアプリケーションをユーザーにお届けすることが可能です。
また、TwitterやFacebookクライアントのWebView内から直接ゲームを開始できるのもブラウザ版の売りとなっています。
ブラウザ版対戦画面
ブラウザ版マイページ
開発リソース
ブラウザ版開発にあたっての開発リソースは次のとおりです。バックエンドに関してはiOSアプリで利用しているJSON-RPのエンドポイントを利用するので主にクライアントの実装となります。
期間
およそ1ヶ月半 iOSアプリリリース後の4月中旬〜5月末まで
ゴールデンウィークを含めるとタイトな開発期間でした。
※マークアップは5割ぐらい進んでいるところからスタート
エンジニア
2名
作業分担
サーバーサイド
Perl +JavaScript(Node.js)
クライアントサイド
HTML + CSS + JavaScript
今回、私はクライアントサイドの方を担当しました。
実装方針
今回はシングルページで実装することを選択しました。その理由は
アプリ内で画面遷移が発生してもBGMを鳴らし続けることができる
アプリケーション内で利用するアセット(JavaScript, CSS, 画像, テンプレート)を初回ロードで全て取得しておくことでそれ以降の画面遷移を高速にし触り心地をアプリに近づけることができる
になります。
最近のブラウザにはWeb Audio APIやAudioタグのような音を鳴らす機能が出揃っています。しかし、画面遷移ごとにリロードが起こる従来のWebページの実装方法をしてしまっては画面遷移のたびにBGMが消えてしまいあまり良いユーザー体験をもたらすことができません。そのため、画面遷移なしで画面が切り替わるごとに必要な情報だけ取得してくるシングルページアプリケーションの実装方法をとっています。
技術選択
メンバーが揃い、実装方針が決まったところでそれをどうやって表現するかというフェーズに移ります。利用するライブラリや言語を選択するにあたり次の点を尊重するようにしました。
極力枯れている技術を使う
検索すれば豊富に資料が出てくる
簡単にソースコードが読める
使い慣れている
開発期間が短いので認知度も高く使い込まれた技術の選択が必要になります。新しい技術を使い技術の習得をすることは面白いのですが、調査や習得に時間がかかってしまい、プロダクトの開発が間に合わなければ本末転倒です。新しい技術を使ってしまうと検索しても情報が少ないですしね。
選択対象となる技術
JavaScriptフレームワーク
AltJS
CSS or canvas
その他
1.JavaScriptフレームワーク
昨今はAngularJSの流行を筆頭に、Backbone.js,Vue.jsなど様々なフレームワークが世に出回っています。
その中でも今回は以下の理由でBackbone.jsの拡張版であるMarionette.jsを選択しました。
使い方がわからない時はさくっと読める
Backbone.js自体枯れた技術なので検索すると色々情報が出てくる
強力なデータバインディングをもつAngularJSも使ってみたかったのですが学習コストが高い点と、CSSアニメーションをバリバリ使ったような複雑なUIを実装するたびに、実装方法に悩んでしまいUIの実装に時間がかかった経験があったので候補から外しました。加えて、現在のAngularJSのバージョンはモバイルファーストではないので、ユーザーの滞在時間が長くなるとメモリが開放されず徐々に動作が重くなる懸念がありました。軽量でメモリの破棄を目で追うことができるのもBackbone.js(Marionette.js)の利点の1つです。
生のBackbone.jsではなく、その拡張ライブラリであるMarionette.jsを利用した理由は、Backbone.jsをそのまま使った時に大量に生まれる同じようなコードを、Marionette.jsが良しなにまとめてくれており、使わないで損はないからです。
※参考
2.AltJS
フレームワークに続きこちらも世の中の変化が早く様々な言語が登場しています。
QuizNowは中〜大規模のJavaScript開発になることが最初からわかっていたため運用などを考えて型安全のあるJSXやTypeScript,Haxeなどを選択したかったのですが、Marionette.jsを始めとしたサードパーティのライブラリを利用しようとすると、言語とライブラリのバインディングを実装する必要がある可能性が出てきてしまい、そこに工数を取られるのは厳しいものがありました。
というわけで、JavaScriptと記法も似ており使い慣れていたCoffeeScriptを利用することにしました。
※参考
isを使った明確な等価演算やクラス定義、書きやすいシンタックスシュガーには終始お世話になりました。
3.CSS or canvas
QuizNowの対戦部分は一般的にイメージされるキャラクターが戦ったりするようなゲームではありませんが、Webページとして表現するにはインタラクティブなものです。
Flashが使えないスマートフォン上でこの手のサイトを表現するにはcanvasで作るかCSSのアニメーションとHTMLで作るかの二通りの方法が考えられると思います。
それらの選択肢のなかで今回はCSS+HTMLの方を選びました。私が使い慣れいているからという理由も大きいですが、CSS+HTMLで実装する場合に享受できるメリットが多い点があります。
メリット
ゲームエンジン等のライブラリを読み込まなくてすむ
JavaScriptとデザインが疎結合なのでデザイン変更が簡単
前者に関してはあくまでWebページなのでロード時間の短縮のため、ロードするファイルの総容量を減らすことは必要最低限努力すべきことです。そのため、できることなら容量の大きいゲームエンジンやcanvas用のライブラリの利用は避けたいところです。canvasを直接触って実装するという荒業もあると思いますが、そういった実装しようとすると、対戦部分の実装だけで工数がなくなってしまいそうなのでCreateJSなどのcanvas向けライブラリを導入を迫られることになるでしょう。
後者に関しては、HTML+CSSで実装されたWebページには更新が簡単という利点があります。CSSを利用することによりデザインとプログラムの関わりを非常に疎結合に保つことができるので、CSSだけ修正すればある程度のUI変更なら簡単に行なえます。それ故に、UIの改修や実験的なUI変更を速いスピードで行うことができ、プロダクト改善のPDCAサイクルを回しやすさに大きく貢献することが可能です。
反面、CSSを選択した場合以下のようなデメリットもあげられます。
デメリット
Android標準ブラウザの挙動差異で地獄を見る
しかしこれに関しては社内にノウハウがまとまっていたので、なんとか乗り切ることができました。あくまでQuizNowぐらいのインタラクション量だったから可能だった選択でしたが最終的に解決できなかったAndroidのバグもあまりでませんでした。
4.その他
他にもQuizNowでは以下の技術を利用しています。
このあたりの技術を使えば、情報も豊富で迷わず開発を進めることができます。
いざ実装!
利用する技術がきまったら実装の開始です。
QuizNowでのクライアントサイドの実装フローを簡単にですが紹介していきます。
CoffeeScriptのディレクトリ構成を整える
テンプレートのひな形を準備
6割ぐらいの画面を徹底的にマークアップ
スタイルガイドを作る
残りのマークアップを終わらせる
CoffeeScriptのロジック実装
1.CoffeeScriptのディレクトリ構成を整える
Marionette.jsを利用するとModel、View、Controllerを分けて記述できる分、細々したファイルが増えてしまいます。最終的には一枚のファイルにまとめるのでそこまで気にすることではないのですが、ディレクトリ階層を丁寧に分けて開発しなければ開発中に、どこにどのファイルがあったかわからなくなり作業効率の低下につながります。QuizNowではMarionette.jsでURLを表現するために利用するハッシュ(#)以下の第一階層を1ディレクトリとして管理する方法をとりました。
例えばカテゴリ一覧のURLでいうところのcategoryが1ディレクトリです。そして、その中にModel,Viewなどをわけて保存しています。
・カテゴリ一覧のファイル
http://quiznow.me/#category
例)
--------
├── category
│ ├── category_module.coffee // ルーティングを定義
│ ├── controller.coffee // modelやviewをnewしていく
│ ├── models
│ │ └── category.coffee
│ └── views
│ └── list.coffee
├── top
├── index
--------
ルーティングの設定までしておくとマークアップに入る前がすごく楽です。
2.テンプレートのひな形を準備
CoffeeScript側のディレクトリ構成が決まったら次はテンプレートのひな形を作ります。
こちらもCoffeeScriptのディレクトリ構造と合わせて、各画面ごとに1ディレクトリします。
--------
├── category
│ ├── category.html // カテゴリページの大枠
│ ├── list.html // カテゴリ一覧のリスト
--------
3.6割ぐらいの画面を徹底的にマークアップ
ここまで準備したらマークアップの開始で、Marionette.jsとLo-Dashのテンプレートを利用した場合は最終的にプロダクトとして上がった際にテンプレートが細切れになってしまうことが多いです。
<li><%= category %></li>
上のような1タグだけのテンプレートが大量に生まれてくることもざらにあります。先にロジックから書いてしまい細切れのテンプレートが大量に生まれてしまってからではHTML + CSSのマークアップで画面全体を表現するときに非常に苦労することになってしまいます。
特にHTMLを書く人とJavaScriptでViewを書く人がわかれている場合はよく話し合ってから作業をしたほうがよいでしょう。
4.スタイルガイドを作る
6割ぐらいマークアップが上がったところで、プロダクト全体で利用できるCSSを切り出していきます。なぜ6割ぐらいのマークアップかというと6割ぐらいのマークアップを済ませて置かなければ、共通のパーツが見えてこないからです。スタイルガイドから作るという方法もあると思うのですが、その方法で開発すると、デザインが上がっているとはいえプロダクトの全体観が見えて来ないので、細々した余白や色の差異などに影響されにくい抽象化されたCSSは書きにくくなります。
また、スタイルガイドに載せたはいいがプロダクト全体を通して一箇所しか使われないスタイルを産んでしまうことも度々発生します。そのため、全体観が見えた時点でスタイルを切り出していきスタイルガイドを作ることをおすすめします。
前のプロジェクトで利用していたため資産が溜まっていたこともあり、QuzizNowではKSSというスタイルガイドを利用しています。
KSSに組み込まれているデフォルトのテンプレートを利用して作ってもこのぐらい見栄えの良い物できます。
開発者が複数人いる場合にコピペで使えるHTMLソースが溜まっていくのは開発の高速化やスキルセットによらない運用への近道につながります。さらに、CSSが抽象化されることによりCSSファイル全体の容量も削減することが可能になります。
時間ができたらスタイルガイドのデザインも丁寧に作りこんでgithubのスタイルガイドぐらいしっかりしたものにしたいものです。
※参考
githubのスタイルガイド
5.残りのマークアップを終わらせる
スタイルガイドの仕込みが終わったらここから一気にマークアップを終わらせます、この時点で様々なパーツが出揃っているのでわりとコピペで進められるところがでてきてスピードアップが可能になります。
6.CoffeeScriptのロジック実装
HTMLが揃ったらいよいよCoffeeScriptでロジックを書いていきます。
既にObjectivie-Cで書かれたiOSアプリのコードが存在しているので基本的にはそのコードを読みながら仕様を漏らさないようにCoffeeScriptを書いていきます。
一例で言うと、アニメーションに関してはCoffeeScript(JavaScript)で逐一DOM要素を動かしていたらとことん処理が重くなっていき、きれいなインタラクションを実装することができないので、CSSのtransitionやkeyframeアニメーションを実装していきます。
例)
Objective-C
CAKeyframeAnimation *Animation = [CAKeyframeAnimation animationWithKeyPath:@"transform.scale"]; Animation.duration=2.0f; Animation.repeatCount=1; Animation.removedOnCompletion = NO; NSArray *sizeValues = @[[NSNumber numberWithFloat:0.0], [NSNumber numberWithFloat:0.8], [NSNumber numberWithFloat:1.0], [NSNumber numberWithFloat:0.8], [NSNumber numberWithFloat:0.8], [NSNumber numberWithFloat:1.0], [NSNumber numberWithFloat:0.8], [NSNumber numberWithFloat:0.8], [NSNumber numberWithFloat:1.0], [NSNumber numberWithFloat:0.8], [NSNumber numberWithFloat:0.0], ]; Animation.keyTimes = timing; Animation.values = sizeValues; Animation.delegate = self; [Animation setValue:self.matchFireLayer forKey:@"layer"]; [Animation setValue:@"hono" forKey:@"MyAnimationType"]; [self.matchFireLayer addAnimation:Animation forKey:@"hono”];
CSS
@-webkit-keyframes animation { 0% {-webkit-transform: scale(0, 0);} 15% {-webkit-transform: scale(0.8, 0.8);} 20% {-webkit-transform: scale(1, 1);} 25% {-webkit-transform: scale(0.8, 0.8);} 55% {-webkit-transform: scale(0.8, 0.8);} 60% {-webkit-transform: scale(1, 1);} 65% {-webkit-transform: scale(0.8, 0.8);} 85% {-webkit-transform: scale(0.8, 0.8);} 90% {-webkit-transform: scale(1, 1);} 95% {-webkit-transform: scale(0.8, 0.8);} 100% {-webkit-transform: scale(0, 0);} } .fire { -webkit-transform: scale(0); -webkit-animation-name: animation; -webkit-animation-duration: 2s; }
上記の様にCSSのkeyframeアニメーションを実装したクラスをアニメーションさせたいタグに付与すればiOSアプリで動いていた動作のままブラウザ上で起動します。
さてリリース
QuizNow(ブラウザ版)はシングルページアプリケーションなので、一度全てのファイルを読み込んでしまえは残りはXMLHttpRequestでJSON-RPCを利用して表示するリソースを取得してくるだけなので最初のロードさえ終われば軽快に動作します。
しかし最初のロードが遅ければゲームに辿り着かずユーザーが離脱してしまう可能性もあります。
ここでは初回ロードの速度を上げるためのTipsを紹介します。
ログインページでのアセット読み込み
画像圧縮
JavaScriptファイル結合・圧縮
1.ログインページでのアセット読み込み
QuizNowはログインが必要なゲームなので、最初にゲームに訪れたユーザーは確実にログイン画面を通過することになります。そのため、ログイン画面にユーザーが来た際にゲーム内で利用する、CSSやアイコンのスプライト画像を一気に読み込ませてブラウザのキャッシュに乗せるようにしています。そうすることによりログインしたユーザーがゲーム画面を訪れた際には既にCSSやアイコンのスプライト画像がブラウザのキャッシュに乗っていることになり、ネットワーク経由で取得するアセット数を減らすことが可能になります。
2.画像圧縮
いかにCSSの行数を減らす努力をしても重い画像が一枚でもあるだけで一瞬でその努力が水泡に帰します。
大抵の場合は画面のロードを軽くするためにCSSやJavaScriptの行数を減らしに掛かる前に画像の重さを計測して、適宜、軽量化を測った方が効率が大きい場面が多いでしょう。今回は以下の2ツールを利用して画像の軽量化を行っています。
減色
不要なチャンク情報など削除して再保存
ImageAlpha利用画面
Compassやその他サーバーサイドの画像圧縮ツールで自動的にやるのが楽かもしれませんが、結局のところ画像の減色は目視で行うのが一番効率が良いとおもっています。ツールで自動化した場合、画像が荒れてデザイナーさんの意図した色が出ていないこともよく起こります。
画像の軽量化に関してはデザイナーさんが行っているチームや私のようなデベロッパーが行っているチームが様々存在していると思います。皆様がどのようにこの作業を効率化しているのかお話を聞きたいところでもあります。
3.JavaScriptファイル結合・圧縮
Marionette.jsで実装するために生まれた細かいファイルは最終的に1枚のJavaScriptファイルとして結合した上で更に圧縮して配信しています。QuizNowでは以下のツールを使っています。
次への課題
ひと通り作り終わってから「こうすればよかった!」と思った課題です。
次に同じような実装をするときまでに何らかの解を見つけておきたいところです。
CoffeeScript,Marionette.jsのジェネレータがなかったので作ればよかった
ViewやModelの細々したファイルを毎回作るのが面倒です。
HTMLが細切れになるのでマークアップとJavaScriptの分業をした運用が難しい
JSが動かないとテンプレートが閲覧できずマークアップとの分業が難しいです。
もっと初回ロードを軽くしたい
まだちょっと遅いです・・・。
まとめ
ざっくりとではありますが、QuizNow(ブラウザ版)での技術選択と開発フローのお話をさせて頂きました。
Marionette.jsだけ、CSSだけに焦点を当ててみてもまだまだお話し足りない事はありますが、記載した判断基準やワークフローが少しでも読者の皆様の参考になれば幸いです。
昨今はゲームも含んだ様々なアプリケーションでネイティブシフトが激しい時勢になっています。しかし、インストールを待たずしてプロダクトの体験をユーザーに提供できる点や、TwitterやFacebookなどでシェアされたURL経由や検索結果からユーザー流入との親和性においてまだまだWebで実装することの優位性が存在する場面は多くあります。
※例えばQuizNowではアプリから面白かったクイズのシェアができるようになっています。
この画面では問題の静止画ではなく実際にブラウザ上で1問クイズを解くことが可能になっています。
これらの点においてもユーザーにとってはより快適に利用でき、開発者にとっては速く開発できより良い運用ができるWebサービスを作る上でのノウハウをためていくことは十分価値があると思っています。
Webのクライアントサイドだけでも新しい技術が次々出てきて、ユーザーを驚かせるようなプロダクトを作れるチャンスがますます広がっています。しっかりとノウハウをため更にWebを盛り上げて行きましょう!
QuizNowはこちらから!