VirtualDOM のパフォーマンステストについて,その後

はじめに

年末に書いたエントリーについて、あちこちで言及がありました*1
師走の忙しさにかまけて勢いで書いてしまったところも多いので、少し補足をしたいと思います。

Virtual DOMってどんだけ早いの?測ってみた - webとかmacとかやってみようか R

まず、最初に断っておきますが、これは Virtual DOM のパフォーマンスを厳密に測定したものではありません。元ネタはこちらです。Blazing-Fast-Html(翻訳: 【翻訳】爆速HTML – Elmでの仮想DOM

AltJS の一つである elm という Haskellっぽく FRP を実現できる関数型言語があります。それを作っている Evan Czaplicki 氏のBlog記事です。彼が作った TodoMVC のパフォーマンス比較が github に公開されているので、それを各ライブラリを最新版にして紹介したのが、上述のエントリーでした。

オリジナルのリポジトリevancz/todomvc-perf-comparison です。すみません、前回リンクを張り忘れていましたね。。まぁ、テストを1回でも実行するとリンクが表示されるのですが。

何を測っているのか?コードリーディング

「このパフォーマンステストはそもそも何を測っているのか?」ということがよく聞かれます。DNSルックアップ?コネクション確立?ネットワーク転送?HTMLパース?JS評価?DOM構築?レンダリング? 現在のWebはページ一つ表示するだけでも様々な要因があります。テストコードが公開されてるのでそれを読めばいいのですが、今回はそこを詳しく見てみましょう。

このテストのメインページはindex.htmlです。ここで読み込んでいるスクリプトは以下の3つ。

  • resources/benchmark-runner.js
  • resources/tests.js
  • resources/manager.js

(※正確には結果のグラフを表示するためにjsapiから GoogleCharts を読み込んでいますが、これはテストとは関係ないために割愛。)

最初にmanager.jsでテストの準備を行います。こちら見ると rパラメータでテスト回数を指定できるようになっているようですね。

テスト対象はtests.jsで定義されています。Suites配列にライブラリ単位でテストスイート追加し、TodoMVC の追加/完了/削除のステップ毎にBenchmarkTestStepオブジェクトを設定しています。

テストの実行はbenchmark-runner.jsに定義されているBenchmarkRunnerが使われます。このstep関数で_runTestAndRecordResults関数を呼ぶことで各テストステップを実行するのですが、テストスイートの初回実行時は iframe にテスト用htmlを読み込み、iframe の onload を待ってから_runTestAndRecordResults関数を呼び出します。

_runTestAndRecordResultsではテストの各ステップを取り出し、_runTest関数で実際のテストステップ(tests.jsで登録したBenchmarkTestStep)を実行します。

_runTestでは、テストステップを呼び出し、その前後の時刻を取得し、実行時間を計測しています。時刻を取得するには Navigation Timing APIperformance.nowが利用可能であればそれを使うようです。

このとき興味深いのが、計測する時間はテストステップ関数の実行時間だけでなく、その直後に 以下のようにsetTimeoutで delay 0 にして非同期の経過時間も取得していました。

    var startTime = now();
    setTimeout(function () {
        setTimeout(function () {
            var endTime = now();
            callback(syncTime, endTime - startTime);
        }, 0)
    }, 0);

これはレンダリング時間を計測しようとしているのでしょうか?それともライブラリ内での遅延処理分を計測するためでしょうか。ちょっとよく分かりません。。教えて詳しい人!

とりあえずこのテストでは、この同期時間と非同期時間を足したものを処理時間として計測しているようです。テスト結果は、各ステップの処理時間の合計をグラフとして表示します。

各ライブラリのテストステップ

各ライブラリのテストステップではおおよそ次のようなことが実行されています。

アイテムの追加

input要素に値をセットして、keydown(またはkeypresskeyup)イベントを keyCode = 13 で発火、というのを100回繰り返します。なお、EmberやAngularでは input要素に値を直接入れるのではなく、Viewモデル経由だったり、inputイベントを発火したりする。

発火するキーイベントや、値のセットがライブラリ毎に微妙に違うのは、おそらくそれぞれの TodoMVC の作り方が違うためだと思われます。

アイテムの完了

document.querySelectorAll('.toggle')チェックボックス要素を取得し、それらを全部click()でクリックイベントを発火させます。

アイテムの削除

対象が '.destroy' になっただけで完了とほぼ同じ。

各ライブラリの TodoMVC

各ライブラリの TodoMVC の実装がどうなっているのか見てみました。基本的には GitHubtastejs/todomvc にあるものをそのまま使っている模様。

一応参考までにリストアップします。

まとめ

だいぶ疲れてきたので、箇条書きでまとめます。

  • 計測しているのは、ネットワーク部分は関係なく、純粋に追加/完了/削除の処理時間のみ。
  • 処理時間は同期処理と非同期処理の合計だが、非同期処理時間の計測にはやや疑問が残る。
  • 各ライブラリのTodoMVC 自体は本家のものやパフォーマンス考慮されたもののため、大きな問題は無さそう。
  • 処理時間はハード/OS/ブラウザの状態に大きく影響されるため、複数回計測した方が良い。

なお、元サイトの画像をよく見ると、

「Average time in milliseconds over 16 runs」 と書いてある。つまり16回やった平均値のグラフのようです。実行回数はr=10のようにURLパラメータで指定します。


同じ VirtualDOM を使っているのに、Om、Mercury、Elm はなぜ React よりも早いのか?についても調べたかったのですが、時間切れのためまた次回。

余談

ところで、この evancz のテストコードも実は別のリポジトリのフォークです。大元は Matt-Esch/mercury-perf。 あれ、この人は、、と気がついた方はさすがですね。React とは別の VirtualDOM 実装 Matt-Esch/virtual-dom を作った方です。Uber で @raynos と共に mercury を作ってるようです。

話が逸れた。で、evancz は Matt-Esch のコードをフォークして、グラフに表示するようにしたようです。テストの実施や計測部分はほとんどいじってないように見えます。

追記

羽田野さん(@futomi)からfacebookでコメント頂きました!

> このとき興味深いのが、計測する時間はテストステップ関数の実行時間だけでなく、その直後に 以下のように setTimeout で delay 0 にして非同期の経過時間も取得していました。


私自身、コードリーディングしたわけではないので、確かなことはいませんが、たぶん、お察しの通り、レンダリングの時間を計測していると思われます。私もよく似たコードを書いたことがあります。

実は、DOM 操作が完了してから、実際にレンダリングが完了するまでには時間差があります。テレビなどの組み込み系デバイスだと、これが顕著に出ます。

しかし、レンダリング完了をスクリプトから知ることができません。では、それをどうやって計測するかというと、ブラウジングコンテキストはシングルスレッドで動作している点を利用します。

レンダリング処理を行っている最中は、シングルスレッドゆえに、タイマーですら待たされます。そこで、DOM 操作が終わった直後に、最短で実行するタイマー(ここでは、setTimeout を 0 msで指定してますが、実際にはブラウザーは 5ms として処理します)をセットします。5ms でレンダリング処理が終わらなければ、このタイマー処理は待たされます。そして、レンダリングが終わり次第、キューに貯められた、このタイマー処理が実行されます。

これでレンダリング完了までの時間を計測することができます。もしレンダリング処理が 5ms 未満で完了してしまうような高性能なマシンだと、計測は若干不利な結果になりるはずです。

ただ、2 回タイマー関数を使っているのは私にも謎です。レンダリングの前に、ブラウザーが何かしているのかもしれませんね。

ありがとうございます!なるほど。やはりそうですか。私もそういったコードをたまに書きます。特にCSSのdisplayプロパティを変更した後など。2回タイマーをネストしてるのはレンダリング完了が間に合わなかった場合のため、2回キューに積んでいるんですかねぇ。Matt-Esch本人に聞いてみたい!

Virtual DOMってどんだけ早いの?測ってみた

この記事は VirtualDOM Advent Calendar 2014 - Qiita の2日目です。

mizchi くんから誘われて軽い気持ちで参加したら、初日からえらくエモいエントリー(VirtualDom - なぜ仮想DOMという概念が俺達の魂を震えさせるのか - Qiita) でブルってます。

Virutal DOMとは、と言う話はしません。初日を見てください。いろいろ良いことあるみたいだけど、Virtual DOMってどんだけ早いの?知りたいですよね。

Elmの中の人が作ったTodoMVCのパフォーマンステストがあります。

いつものTodoMVCのデモで、100要素追加して、全て完了して、削除するというテストです。
「Run All」ボタンをクリックすると動きます。
http://evancz.github.io/todomvc-perf-comparison/

Virtual DOMを採用している Om, Mercury, Elm は圧倒的に早い!Angularは、、まぁこういうの苦手だよね。。 (localStorageに値を保存するので、一度クリアしたほうが正確に測れます)

ん?ちょとまて! React も Virtual DOM使ってるんじゃなかったのかよ。
Backboneに負けるのかよ!!

よく見ると、React のバージョンが 0.10.0 とちょっと古いようです。(2014/12/02現在最新は 0.12.1)
どうせなので、全て最新版にするとどうなるのかForkして作ってみました。
http://dsuket.github.io/todomvc-perf-comparison/

f:id:dsuke:20141202133927p:plain
んー、React 微妙な感じ。。。

Mercury と Elm は Matt-Esch/virtual-dom 実装を使っているのですが、Om は内部的に React の Virtual DOM を使っています。この違いはなんなんでしょうね。。

Om, Mercury, Elm は、関数型で Immutable なデータ操作を行います。もしかしてしてこれがJITコンパイラとかに効いているのかな?私が試したのは Chrome 39 でした。他のブラウザでは違う結果になりそうですね。

初日のエントリーにもあったように、Virtual DOM は Immutable なデータ構造と非常に相性が良いです。特にデータをストリームとして扱う FRP では強力ですね。

ところで、最近 MVI(Reactive MVC and the Virtual DOM — Futurice) のような話しもありました。また新手の MV* かよ、MVVMの派生系じゃないのかと思ったのですが、FRP のパターンとしてみると使えそうな気もします。そうみると、以前私は Flux は要らないといったのですが、これも FRP に当てはめると良さそうですね。Bacon.js で Flux を作ってみた例(Flux inspired reactive data flow using React and Bacon.js - Reaktor)なんか面白そうです。

最後は憶測で雑でしたが、そういったアーキテクチャを支える技術として Virtual DOM があります。逆に言うと、Virtual DOM のおかげで新しいアーキテクチャが実現可能になりました。まだまだ発展途上ですので、今後どんなアーキテクチャフレームワークが産まれるのか楽しみですね!

結論:
Virtual DOM 使うなら関数型(FRP)!
elm最高!!

追記

フォロー記事書きました。

VirtualDOM のパフォーマンステストについて,その後 - webとかmacとかやってみようか R

jQuery Mobile & Sencha Touch ハンズオンやりました

昨日、こちらのハンズオンイベントで講師させて頂きました。

AITC第2回勉強会「jQuery MobileとSencha TouchでWebアプリを作ってみよう!」~ 2大スマホ向けWebアプリフレームワークを使いながら比較 ~

猛暑の中、30名近い参加者にお越し頂き、ありがとうございました。

IT関連ではない会社や、マーケティング部などの非エンジニアの方々や、遠方よりこのために参加頂いた方々、学生さんなどなど、多種多様な方に参加頂きました。

資料のまとめは前回のエントリで。
jQuery Mobile & Sencha Touch ハンズオン資料まとめ

ある程度予想はしていましたが、やはり環境構築が鬼門でした。今回、node.js+express や、ruby + compass など非エンジニアの方には馴染みが無いツールを使う演習のため、本質的では無いところで時間がかかってしまったのがやや残念です。

とりあえず勉強会として、良かった点、改善すべき点などをまとめてみます。

良かった点

jQuery Mobile と Sencha Touch を同時に扱うというテーマはなかなか面白かった

同時に見比べられるので、非常に違いが分かりやすいと好評を得ました。1回で両方できるお得感はあったので申し込んだ、欲張りすぎるww などの声も頂きました。

実は、当初は3つにするとか、もっとオープンデータを使うだとか、かなり要望は膨らんでいましたw
なんとかコンパクトにして、あのボリュームでした^^;

どちらも一通りの流れを、順を追ってできた

段々機能を追加していき、完成度を高めていくようなサンプルが良かった、という声も。
当初は完成形だけ用意して、部分コピペで進めていこうかと思ったのですが、初心者が多そうだという事前情報から、前日に段階的にできるサンプルに作り替えました。進める側からも、これはやっておいて良かった。あの状況だと部分コピペで進めるのはかなり厳しかったでしょう。。。

コピペでもとりあえず手元で動かせた

なんとか全員環境を構築できたので、手元で動かせる環境が作れて良かったです。
とりあえずは演習資料をそのままコピーでも、動かせる環境が手に入った、というの大きかったのでは、と思います。これできたので、あとはサンプルをちょこちょこいじりながら自分で勉強することもできます。あと、エディタも今回初めて Sublime Text を使ってみたけど良かった、という方も。

反省・見直しが必要な点

環境構築はアナウンスとかやりかたをもっと考える必要あり

今回演習の中でそれぞれ環境準備を20〜30分ほど予定を入れていたのですが、全然足りませんでした。演習の中に、特に最初に入れてしまうと、全体の進行が止まってしまうため、あまりうまい方法ではなかったですね。

インストールできてない方は早めにきてセットアップしてもらうとか、出来ている人は演習を進められるような流れにすれば良かった、という反省があります。そうすれば、進められる人は自分でコードをカスタマイズしたりして理解をもっと深められたかなぁ、とも思いました。

また、事前インストールの件があまり告知しきれてなかった問題もあります。イベント告知や申込が色々と分散されたのも情報共有漏れが起きる原因の一端ですね。この辺りは運営側の事前準備不足でご迷惑をおかけしました。

全体的にやはり詰め込みすぎ

環境構築もさることながら、演習内容もかなり詰め込み過ぎた感はあります。特に後半は早すぎて理解が追いつかない、とか自分でコードを書く暇が無かった、ということに。もっと自由にコードを書いてもらう時間を作りたかったですね。。

2つ同時にやるというテーマ自体は評価されたのですが、ハンズオンとするとどこまで踏み込むか悩ましいところです。コピーしてなんとなく動いた、というだけじゃ面白くないですし、どこまで解説するか難しい。

Sencha Touchが難しい印象を与えてしまったかも。。

みんなに言われたのが、Sencha Touchが難しすぎる、と^^;;
これは私の伝え方が良くなかったです。すみません。。言い訳するとちょっと時間がなさ過ぎた。。。ま、参加者の半数くらいは聞いたことない、というので仕方ないところも。しかし、JavaScriptに馴染みが無い人に言われるのは仕方ないですが、JS書く人にも言われたのはややショック。Sencha Touchを簡単に教える方法教えてください・・・・

その他

・アンケート出し忘れた。。 参加者の方には後日Webアンケートの案内をお送りします。
・後半、自分がだいぶ息切れして、ちょっとテンション落ちてしまってた^^;
・教室の温度がちょっと暑かったかな

総評

反省すべき点も多々ありますが、一応予定していた内容を全て時間内に終わらせ、参加者の皆さんにも開発環境とコードというお土産を渡せることができたので、まずは良かったんじゃないでしょうか。
私自身も大変勉強になりました。
参加者の皆様、スタッフの皆様、長時間お付き合いいただき、ありがとうございました!お疲れ様でした!

このカリキュラムをもうちょっと改善して、またリベンジしたいですね!w

jQuery Mobile & Sencha Touch ハンズオン資料まとめ

いよいよ明日に迫ったAITC第2回勉強会「jQuery MobileとSencha TouchでWebアプリを作ってみよう!」

半日でどっちも触ってアプリ作るという無謀な企画です。なんとか資料作成間に合いました。
とりあえず Slideshare に上げたのですが、バラバラとなってしまったので、こちらでまとめます。


最初の資料


まずは jQuery Mobile概要から


jQuery Mobileのハンズオン資料

続いて、Sencha Touch概要


Sencha Touchハンズオン資料


まとめ


なお、サンプルコードはこちら。Sencha Touch を含んでいるためGPLとなってます。


全部合わせると、総ページ数 91枚とかなってた。。
どおりで終わらないわけだw

しかし、これどこまでできるのだろうか。。。
戦々恐々です。

AITC第2回勉強会「jQuery MobileとSencha TouchでWebアプリを作ってみよう!」のご案内

そういえば、告知が遅くなりましたが、来週末の土曜日 7月13日(土)に、下記ハンズオンで講師を務めさせて頂きます。

AITC第2回勉強会「jQuery MobileとSencha TouchでWebアプリを作ってみよう!」

概要
 実際にコードを書きながら、スマホWebアプリを作るハンズオン形式の勉強会です。2部構成で、前半はjQuery Mobile、後半はSencha Touchを使用します。同じテーマのアプリを異なるフレームワークを使うことで相互理解を深めます。

お申し込みは Facebookイベントページより。

AITC会員企業外の方は有料になります。
事前登録をすると、いまなら早割が利用できてお得です!
今週末までなので是非お早めにどうぞ〜

内容は、jQuery Mobile と Sencha Touch で同じアプリを作って、それぞれの作りの違いを実感しよう!という感じです。お題は、気象庁防災XML を使った、防災速報なようなものを予定しています。

ただ、スケジュールを見てもらうと分かるのですが、各2時間くらいしか時間が取れません。。。2時間でこの巨大なフレームワークを一通りやるのは相当にハードです。なので、細かい説明よりも、とにかく手を動かして実際に動くものを作ってみる、という感じなるかと思います。写経になるかもしれませんが^^;

細かい質問は懇親会、または @dsuket へでも。
作ったサンプルアプリはまた後ほど公開したいと思います。

是非 参加お待ちしております〜。

HTML5機能を色々使ったスマホ向けWebアプリを作ってみた話:後編

前回のエントリでもお知らせしたように、ドコモゼミ Webアプリラボのコンテストにアプリを出しました!面白かったら下記ページの いいねをお願いします!

かきまる

スマートフォンを使いながらも、アナログな感触をもっと楽しんで欲しい。そういう想いで考えられた、デジタルとアナログを繋ぐお絵かきWebアプリです。

.

で、今回のエントリでは 前回のHTML5機能を色々使ったスマホ向けWebアプリを作ってみた話:前編 で書き切れなかった残りを解説します。

今回は次のトピックス

  • かわいい文字で表示したい。
  • 音があると楽しい。
  • スマホとタブレット両方対応。AndroidでもiPhoneでも。

Webフォントでオリジナルフォントを使う

Androidがいけてないの一つが、フォントにあると思っています。標準フォントや入っているフォントやが機種によりバラバラ。しかも標準フォントがダサイのが多い。あまりに違うためデザイン的なページの場合、テキストで統一的に表現するのは難しく、画像にしないといけなくなります。

画像にすると色々問題があります。まず変更がめんどくさい。文言ちょっと変更する度に画像を書き出さないといけない。さらに、昨今の高精細ディスプレイ用に2種類の解像度の画像が必要。そしてモバイルの場合特に重要なのが、ページサイズが増えること。画像が多くなると必然的にページサイズが増え、3G回線などでのモバイルではロード時間が遅くなってしまいます。

じゃあ、どうすればいいのか。そういう時に、Webフォント が使えます。Webフォントとは CSS3 から導入された仕様で、これまではOSにインストール済みのフォントしか使えなかったのが、必要に応じてWeb上からフォントデータをダウンロードして表示できます。これを使用すると、機種毎にバラバラなAndroidでも、iOSでも、PCブラウザでも同じフォントで表現できるようになります。

ただ、日本語フォントのように膨大な数があるとフォントデータが増え、画像データよりむしろ重くなることがあるので注意が必要です。必要な文字だけのフォントデータのする、などの工夫が必要です。今回のかきまるでは、ひらがなのみなのでサイズは woff 形式で11kに収まりました。この程度なら、画像を複数枚用意するよりも少ないサイズでいけます。また、フォントデータをキャッシュさせれば、別の文字が増えたとしても新たなダウンロード時間は増えません。

前回のひらがなの書き順を表現するため、文字データをSVGで作っていたので、それを元にWebフォントにしました。Webフォントに使える形式は幾つあります。最近だと WOFF が標準ですが、古い形式のために ttf や svg形式のものも用意し、CSS記述でフォールバックするようにしておくとベターです。

SVGからWebフォントの作り方はこちらを参考にしました → アウトラインのSVGからフォントを生成 #かな書いてみるFontForge の出力形式に woff、svgなどを追加すると、自動でその形式で出力してくれます。

ちょっとはまったのが、フォントデータにするときはアウトラインデータの方が良い、ということです。まぁ、当然なのですが。。。アウトラインから変換することを前提としているので、stroke の lincap や linejoin などは無視されます。今回の文字データは、書き順アニメーションのために1本のパス(stroke)で書かれていたのですが、そのまま変換するとうまくいきませんでした。そこで、フォント用に別途アウトラインデータも作成しました。

音を再生する

photo by vancouverfilmschool (CC BY 2.0)

Webで音を再生するには二つの方法があります。簡単なのが HTML Audio要素を使うこと。スクリプトからも操作でき、簡単な音声の制御ができます。もう一つが Web Audio API を使用すること。こちらはもっと複雑なことが可能で、合成やフィルタリングなどを使用してWeb上でシンセサイザーのようなことも実現できます。

ただし、Web Audio API はモバイル端末ではまだまだサポートが厳しい現状です。iOS 6から一部制限付きで使えるようになりましたが、Androidでは全然だめ。ということで、スマホ向けWebアプリは 今のところ HTML Audio要素一択という状況です。

HTML Audio要素ならスマホでも盤石か、と言うとそういうわけでもありません。スマホの限られたリソース(CPUや帯域)では、音声データのダウンロードやデコードで少し時間がかかる場合があります。そこで予めデータをプリロードしておきたいところですが、これも色々と制限されています。詳しくは 過去エントリ iOS/Android で HTML5 の audio/video を任意のタイミングで再生する方法 でも書いたのですが、audio を play する前に、load しておくことで対応できます。

「かきまる」では、当初 最初のページにタッチしたタイミングで全音声データをロードしていたのですが、それだと重すぎて最初のページめくりが固まってしまいました。そこで、各ひらがの書き順ページへ遷移するタッチイベントをトリガーに、次の動物の名前の音声データをロードするようにしました。書き順を表示している裏でダウンロードされるため、次の動物にいったときには、すぐに再生されます。

この方法の欠点として、書き順を表示しきる前に次々にページをめくると音声再生が追いつかない、という問題がありますが、これはもう致し方なしとして諦めました。また、Android Chrome の ver 25 くらいまではこの方法でうまくいっていたのですが、最新版の 27にすると事前 load が効かず、play時にのみロードされるようになってしまいました。

他にも、Galaxy Nexus(Android 4.2.2)の標準ブラウザと Chrome を組み合わせると、ブラウザ終了時やスリープ時に最後に再生した音声が勝手に再生される、というとんでもないバグがあったりします。しかも通知でも再生されるため、メール着信などある度に ”おおありくい〜” とか勝手着信音となってしまったり(ーー;; 他の端末では起きなかったため、対処は見送り。。

スマホ/タブレット/Retina対応。Androidでも。

一通り動くようになってから、最後に スマホ(縦横)と、タブレット(縦横)でもちゃんと見えるよう、CSS メディアクエリを使って調整しました。Android でもきちんと表示されるよう対応させるのに苦労しました。

SVGを画像として使う。

画像をスマホ/タブレット用に最適化すると、それぞれのRetina用も考えると、スマホ or タブ x 2解像度 = 4パターン必要になります。さらにそれの縦横まで対応すると、1つの画像に8パターンも必要になってしまいます。(実際には縦横は同じ画像を縮小で表示することが多いでしょうが)

今回は画像に SVGを使ったため、1パターンさえあればよいので非常に楽でした。Android 2.x は SVGに対応していないのですが、ここはもうばっさり切りました。Android 4以上なら問題なし!と言いたいところですが、まだ落とし穴があります。

SVGをWebページに埋め込みで使う場合、幾つか方法があります。

  • OBJECTタグで使う
  • IMGタグで使う
  • インラインSVGにする

古いブラウザなどの場合、IMGに使えなかったりするので、OBJECTタグを使用する必要がありますが、スマホの標準ブラウザ(Chrome)ならIMGで問題ありません。OBJECTタグにする問題点は、外部ドキュメントになるため、UIイベントがそちらに取られてしまうことがあります。また、インラインSVGにすると元ページにSVG要素を埋め込まないといけないため、画像の差し替えが煩雑です。なので、単に画像として使う場合は IMG要素として使うのがオススメです。(この節のSVGと書いてある画像も実はIMGタグでsvgを指定しています)

IMGタグでSVGを使う場合、画像サイズの指定はIMGタグで行います。width/heightの属性か、CSSの属性で指定できます。通常、widthとheightを指定すると、元画像のアスペクト比を保持したままスケールします。しかし、一部のAndroid標準ブラウザでは、アスペクト比が保持されず引きのばされてしまいます。iOS と Android標準ブラウザの表示違いは下の図のようになります。

Android標準ブラウザ

そこで svg ファイルの viewBox を見直しました。svgタグには、どの範囲を表示するか指定する viewBox という属性があります。このviewBox のアスペクト比と実際に表示させる画像のアスペクト比を一致させることで、この問題に対応することができます。

アイコン

一部のアイコンにも、SVGフォントを使いました。IcoMoon などを使えば簡単に導入できて便利です。ただし、Androidでは要注意です。

通常、Webフォントをアイコンに使う場合、特定の文字コードをアイコンに割り当てます。Unicodeでは、外字エリアとして私用領域という名称で U+E000〜U+F8FF、U+000F0000〜U+000FFFFDなどが割り当てられているので、これを使うことが多い。前述の IcoMoon はこの外字エリアを使っていて、U+E001 から埋めていきます。

ところが、一部Andoroid(Softbank端末など)は、この外字エリアに独自のアイコンを定義してたりします。→ 携帯電話の絵文字

U+E001 は ソフトバンクの絵文字ともろに被っています。今回は時間が無かった & ドコモ向けなので、残念ながら対応は見送りましたが、IcoMoon などを使う場合、コードには十分気をつけてください。

メディアクエリ

SVG画像のスケールは上記の対応で可能になりました。次は、メディアクエリを使い、幅に応じて画像や文字の大きさを変更します。メディアクエリで端末サイズを判定するには主に二つの方法があります。

  1. min-width, max-width で判定する。
  2. min-device-width, max-device-width で判定する。

1つ目の方法は、windowの幅で判定するのに対して、2つめは、端末の幅で判定します。PCブラウザから見るとどちらも同じように動きますが、スマートデバイスでみると大きく異なります。特に回転した場合に違いが出ます。デバイスの向きを横に回転したとき、1の方法で横幅の変更を検知し、レイアウトを変更できます。しかし、2つめの方法では端末自体の幅は固定のため、回転しても変更されません。そのため、向きを判定するためには orientation:portrait / orientation:landscape を組み合わせて使用します。 なお、この orientation は厳密には向きを見ているのでは無く、縦長か横長なのかを見ているだけです。なので、PCブラウザで縦長/横長を変更しても反映されます。

どっちの判定を使えば良いか、というと前者をオススメします。というのも、これまた一部のAndroid標準ブラウザでは、device-width を正しく認識しません。例えば、Galaxy Nexus, S2, S3 なんかでは、window.innerWidth は 360px なのに、メディアクエリの device-width は 320px として認識されます。細かい調整をする場合、正しく認識されないのは致命的です。また、device-widthを使用する場合は orientation も同時に指定しないと意味が無いため、記述も長くなり、お勧めしません。

まとめ

Webアプリでも表現が幅がだいぶ広がってきました。今後、WebRTC や WebGLとか使えるようになったらもっと凄いものができるようになると思います。まだ Android Chromeでもデフォルトはオフ、Safariは全然うごかない(今日発表の iOS7 で対応がでるかも!?)

現状では「かきまる」のようなシンプルなものでも、意外と苦労しています。スマホ向けWebアプリで、なめらかなUIや、複数端末への対応は骨が折れます。特にAndroidは本当にツライのでGoogleさんマジでなんとかしてください(切実

HTML5機能を色々使ったスマホ向けWebアプリを作ってみた話:前編

昨日、ドコモがやっているWebアプリコンテストのノミネート作品が一般公開されました。

私も、デザイナさんと協同して作品を一つ出しました!面白かったら下記ページの いいねをお願いします!

かきまる

スマートフォンを使いながらも、アナログな感触をもっと楽しんで欲しい。そういう想いで考えられた、デジタルとアナログを繋ぐお絵かきWebアプリです。

.

子供達が家の中でスマホだけをいじって遊ぶのでは無く、実際のリアルな紙と色鉛筆で本物の感触で遊んで欲しい。そのきっかけをスマホアプリで与えられないか?というモチベーションで作られました。そのあたりのコンセプトはデザイナさんのアイディア。私は主にその実装担当です。

そういう狙いはあるのですが、簡単に言ってしまえば紙芝居形式で動物の絵を表示して、それをお手本に絵を描く、という至ってシンプルなアプリです。

何も考えずに作れば、複数のWebページを遷移する単なるWebサイトとしても実現できます。しかし、それじゃあつまらないので、今回はそこに色々と実験的な要素を盛り込みました。

  • ページめくりをかわいくしたい。
  • ゲーム的な要素もちょっとあると面白い
  • ひらがなの書き順も同時に教えられたら。
  • かわいい文字で表示したい。
  • 音があると楽しい
  • スマホとタブレット両方対応。AndroidでもiPhoneでも。

これらの要望を HTML5 Webアプリとしてどうやって実現するか、が私の課題でした。今回のエントリーではその実装について簡単に解説します。要望があれば連載で詳しい内容も続けていければと思います。

カードをめくるように表現する

スワイプで次のスライドが表示されるようなアプリは良くあります。Webでもそういう表現が増えてきました。多用されているためやや食傷気味で、そのまま使うのは面白くない。ただ、スワイプジェスチャーでページをめくるという認識は一般にもだいぶ浸透しています。そこで、そのジェスチャーはそのままに、表現をすこし工夫しました。ページをカードに見立て、カードをめくるように次々と下のカードが出てくるような表現にしてみました。

スライドのライブラリは沢山あるのですが、カードのような表現ができるライブラリが見つからなかったため、一から自作しました。タッチイベントのドラッグを処理するには、jQuery プラグインの Hammer.js が便利です。それに CSS Animation を組み合わせて実現しています。

使ってみると、予想以上にカードページングはスマホと相性が良い気がします。後から気がついたのですが、スマホ用 Google Chrome のタブなどもカードアニメーションしていますね。このカードページングは jQuery プラグインとして作成したので、整理できたら公開してみたいと思います。

スロットゲーム

動物をしりとり形式で繋げよう、というアイディアは当初からあったのですが、最初の動物(文字)をどうやって決めようか、と悩みました。ここにゲーム要素を持ってくることにしました。ひらがながスロットのように表示され、タップすると少し遅れて止まります。このスロットもライブラリとして作成したので、公開できればしたいと思っています。スロットの向き、速さ、止まるタイミングなどを色々検討するため、各種設定値で変更できるようにも作りました。

このスロットの中身の文字は、実は普通のテキストです。当初、画像にしていたのですが後述のWebFontを使うことで、文字だけで表現することができました。

また、スロットのタップイベントにも一工夫が必要でした。実際に子供達に使ってもらうと、タップが反応しない、という問題が起きました。スロットを止めるという動作の特性上、狙って押すと結構力が入ってしまうんですね。そうすると、タッチがぶれるんです。ぶれると、タップイベントではなく、ドラッグイベントとして認識してしまうというのが原因でした。これは、以前 @IT にも書いた記事 タッチUXを実現する7つのポイント で、自分も指摘していました。自分で書いてるのに考慮漏れとは情けない。。。

このタップの問題も、前述の Hammer を使うと各種閾値を調整すれば解決できます。ついでに、アイコンやボタンなどについてももう一度当たり判定エリアや閾値を見直し、子供でもタップしやすいように調整しました。

ひらがなの書き順を表示する

ひらがなの書き順をアニメーションで表現するのには、少し悩みました。一番実装が簡単なのは、各コマを用意してcanvasにパラパラアニメのように順次描いていく方法です。しかし、それでは文字が増えたときに大変です。そこで次に考えたのが、SVGのモーフィングを使うことです。Raphaël を使えば、割と簡単に実現できます。しかし結果からいうと、これはうまくいきませんでした。モーフィングの補完をちゃんと指定すればいいのですが、全文字でこれを何コマか設定するのはとてもやってられません。

そこで、ひらがなをグリフではなく、単1のパスの組み合わせとして作ることにしました。それをSVGのパスで表現し、書き順でパス要素を上から並べます。「あ」ならば、1つめの要素が横1本のパス、2つめの要素が縦1本のパス、3つの目の要素が、ぐるっとまわるパス、というような感じです。先ほどの Raphaël を使えば、長さを指定して、パスのサブパスを取得することができます。これを使用して、各パスのサブパスを段々伸ばしながら描画しています。

この文字データを作る際に、どうせならあ段だけじゃなくてひらがな全文字作ろう!といってデザイナさんが頑張って作ってくれました!当初50文字だけかと思ってたら、濁音とか半濁音とか、小さいかなとか意外とあって大変そうでした^^; ここでかなデータをSVGで全部作ったので、これを元に SVG Fontをつくり、後編の WebFontへと繋がります。



簡単に説明するつもりが、予定以上に長くなってしまいました。
ということで、続きは後編で!

→ 後編書きました! HTML5機能を色々使ったスマホ向けWebアプリを作ってみた話:後編