成功するPhoneGapアプリを開発するための高速化&UXテクニック
興味深いブログ記事が海外で掲載されていました。拙訳で恐縮ですが紹介したいと思います。
内容はPhoneGapアプリを高速化 するための手法の解説で、具体的な事例とともに、いくつかのテクニックの紹介が行われています。少し長い記述になりますが、是非PhoneGapやMonacaを用いた開発の参考にしてください。
成功するPhoneGapアプリを開発するための高速化&UXテクニック
Performance & UX Considerations For Successful PhoneGap Apps
PhoneGapアプリを開発する方から、下記のような質問をよく尋ねられます。
- ・アプリを高速化する方法は?
- ・どうやってネイティブアプリのような質感を出せるか?
- ・プラットフォームに違和感のないアプリを作るためのテクニックは?
- ・OSのルック&フィールとマッチさせるためには?
この記事では、素晴らしいPhoneGapを開発するためのテクニックを紹介しつつ、「interweb(Webを取り込んだアプリ)」にまつわる疑問や迷信を明らかにしたいと考えています。
アプリ速度とは、エンドユーザーが感じるパフォーマンスであり、アプリの反応性を意味します。PhoneGapアプリはHTMLとWebViewで構成されているため、各OSプラットフォームが持つWebViewの速度に依存することになります。これは決してHTMLベースのアプリが本質的に遅いという意味ではありません。事実、HTMLベースのUIで成功したアプリは数多くあります。また、WebView自体を高速化することは困難ですが、HTML実行を高速化することは可能です。
一方で、もしWebViewが十分に高速でないと感じる場合は、その部分だけネイティブで組み合わせることが可能です。こうすることで、ネイティブUIを用いながら、必要に応じてPhoneGapによるHTMLとJavaScriptのUIを組み合わせることが可能です。
HTMLとWebViewのパフォーマンスについて
まずは、モバイルにおけるHTMLとWebのパフォーマンスについて紹介します。ここには多くのテクニックが蓄積されており、そのなかには大きく効果を発揮する内容が多くあります。
ハードウェアアクセラレーション
多くの記事で、可能な限りハードウェアアクセラレーションを強制することを推奨する記述があります。その具体的な方法は、下記のようにCSSのtransformのスタイルにtranslate3dを用いるというものです。
transform: translate3d(0, 0, 0);
こうすることでHTML DOMの描画にGPUが用いられるようになります。一方で、この設定がアプリケーションにどの程度のパフォーマンス向上を与えるか、理解しておく必要があります。アプリケーションの速度が大きく向上することもあるでしょう。しかしながら、時としてパフォーマンスに問題が発生し、その原因を突き止めるのに非常に苦労する場合もあります。そのため、あまり考えずに本設定をすべてのHTML DOM要素に対して適用しないことが重要です。
translate3dを用いた場合、GPU上のメモリーに描画内容が格納されます。過度にHTML DOM要素の描画にGPUを用いると、GPU上の利用可能メモリーが埋まってしまう可能性があります。こうなると、突然アプリケーションがエラーメッセージも表示せずクラッシュしてしまうか、GPUのメモリースワップが端末メモリーや記憶媒体に対して発生してしまいます。いずれの場合も、GPUを用いない場合と比べて大幅にパフォーマンスが低下してしまいます。
また、translate3dを用いたtransform操作を適用するたびに、HTML DOM要素の描画データがGPUに転送されます。通常この処理にかかる時間は微々たるもので、気にする必要はありません。しかし、大きく複雑なHTML DOM要素に対してtranslate3dを使用した場合は、最新の端末とOSを使った場合においても、体感できる程度の遅延が発生することになります。HTML DOM要素が複雑になるほど、この遅延は大きくなります。その結果、転送中はWebViewのUIスレッドは完全にロック状態 となります。これまで私が経験したなかで1秒を超えるケースはありませんが、500ミリ秒のUIロックでも深刻なマイナスの印象を与えてしまいます。さらに、DOM要素に変更を加えた場合、その部分が再度GPUに転送されることになります。
translate3dを用いる場合には、DOM要素をネストさせることも控えましょう。たとえば、
また、GPUの最大テクスチャーサイズも考慮する必要があります。もしDOM要素がGPUのサポートする最大テクスチャーサイズ(横幅・縦幅のいずれか)よりも大きい場合、パフォーマンスの低下や品質の低下が発生します。translate3dを用いたアニメーションやスクロールの際に、HTML DOM要素の描画がちらついた経験はありませんか?これはDOM要素の縦横サイズがGPUのサポートしているテクスチャーサイズよりも大きいことが原因です。多くのモバイル端末の最大テクスチャーサイズは1024x1024ですが、OSにより異なります。iPad 2の場合は2048x2048で、iPad 3/4の場合は4096x4096となります。もし最大テクスチャーサイズが1024ピクセルで、DOM要素の高さが1025ピクセルだった場合、画面がちらついたり速度低下が発生したりします。
参考: OSによっては、ブラウザーの実装が原因で描画がちらつくことがあります。この場合、backface-visibilityというCSSプロパティーを設定することで解決する場合があります。詳しくはこちらの記事(英語)を参考にしてください。
最後に、モバイル端末の特徴を再確認することをお勧めします。一般的に、デスクトップPCと比べて、より低速なCPU、バス転送速度、そしてメモリー容量に制限されています。デスクトップPCでtranslate3dが問題なく動作しても、モバイル端末では同じように動作するとは限りません。また、プラットフォームによってハードウェアアクセラレーションの対応度に違いがあり、そのパフォーマンス結果はまちまちです。
translate3dを効果的に用いると、必ずパフォーマンス向上にとってプラスとなるでしょう。一方、使い方によってはマイナス要因となる可能性もあります。上手に活用し、必ず・必ず・必ず、実機でのテストを行いましょう。
コンテンツのリフローが与える影響
「リフロー」という言葉を初めて聞いた方は、本項目に注目してください。リフローとはブラウザーエンジンの処理内容の一つで、HTML DOM要素の位置や座標を計算する処理となります。たとえば、各DOM要素の横幅・縦幅の計算、テキストの改行処理、相対的な位置計算などがリフロー処理の例となります。
リフローの計算は負担が大きいものなので、効果的にパフォーマンス向上を実現するためには、リフロー処理の頻度を最小限に止めることが重要です。HTML DOM要素の横幅を変更するようなアニメーションを作成し、フレームレートが5fps程度となってしまうのは、このリフロー処理が原因と考えられます。
リフロー処理は、DOMの内容を変更する、DOM要素をリサイズする、CSSによる位置変更や余白の変更を行う、といった場合に実行されます。デスクトップPCでは気にならない程度の処理時間ですが、モバイル端末においてはパフォーマンス低下の大きな要因となります。しかし、静的なページでない限り、動的に変更する際のリフロー処理を完全に無くすことはできません。そのため、リフロー処理の負担を軽減する方法について紹介します。
リフロー処理に関する詳細は、下記の記事を参照するといいでしょう。
PhoneGapアプリ開発のテクニックを紹介する多くの記事で、「DOM要素を最小限にとどめること」や「深くネストしたHTMLを避ける」こと、CSSアニメーションや画像のプリロードを行うこと、などが推奨されています。これらはすべて、リフロー処理を最小限にとどめるためのテクニックとなります。
・DOM要素の数を減らす
DOM要素の数が減るほど、リフロー処理において測定・計算する対象要素が減ります。
・深くネストされたHTML DOM構造を避ける
HTML構造が深くなるほど、リフロー処 理はより複雑になり、計算量が増大します。さらに、末端の要素における変更がすべての親の階層に至るリフロー処理を発生させ、より多くの計算量が必要になります。
・CSS transformを用いる
先述のハードウェアアクセラレーションに関するテクニックに加え、CSS3のtransformを用いるとリフロー処理を行わずにHTML DOM要素を変更することができます。たとえば、X・Y・Z軸に対する変換や拡大・縮小処理、回転処理などです。
・CSS AnimationとCSS Transitionを用いる
CSS AnimationとTransitionを用いると、一気に高速化されます。しかし、すべての場合で有効である訳ではありません。リフロー処理を引き起こすCSS操作を行った場合(たとえばwidthやheightプロパティーの変更など)パフォーマンスの低下が発生します。こういう場合は、上述のCSS transformを用いることで、リフロー処理を抑制できます。
・DOM要素に対して固定の幅と高さを設定する
コンテンツのサイズを変更しない場合には、リフロー処理は実行されません。これは<div>要素だけでなく、画像を読み込む際にも該当します。画像サイズが固定されず、画像の読み込みが行われた場合は、読み込み完了のタイミングでリフロー処理が実行されてしまいます。複数の画像が用いられている場合は、その都度リフロー処理が実行されます。
・CSSスタイルで用いられる画像を事前に読み込む
これには2つのメリットがあります。まず、画像が必要になった際に既に利用可能となります。これにより、表示遅延やちらつきを抑えることができます。そして、事前に画像などを読み込んでおくと、画像な どが読み込まれた後に実行される2回目のリフロー処理(1回目はDOMが最初に計算された際、2回目は読み込みの際に発生します)を回避できます。
・HTML DOM要素を賢く使う
たとえばJavaScriptの配列で格納されたデータに対して、<table>要素を作成することを考えてみます。事前に%lt;table>要素を設置し、毎回のループで既存のDOMに各行を追加する処理は、非常にコストの高い処理となります。こういった場合は、まずはJavaScriptの配列から、%lt;table>要素内のHTML DOM要素を作成してしまいます。次に、そのループ完了後に%lt;table>要素を既存のHTML DOMに追加します。こうすることで、リフロー処理を最小限に抑えることができます。
このように、DOM要素のレイアウト計算や位置計算の処理を減らすことが、パフォーマンス向上につながります。
グラフィックはシンプルに
デザイナーの方はすばらしい見栄えのモックアップを作成しますが、それらのデザインに忠実に従ったアプリはパフォーマンス低下の原因となります。CSSシャドゥやCSSグラデーションを使いすぎると、プラットフォームやブラウザーによっては速度低下が発生します。たとえば、これらの効果はiOS端末と比較して、Android端末のパフォーマンス低下が顕著です。
タッチ操作
「マウスイベントは遅くタッチイベントは速い」という内容を聞いたことがあるでしょう。これは事実で、「mousedown」「mousemove」「mouseup」もしくは「click」イベントを使わず、「touchstart」「touchmove」そして「touchend」イベントを利用しましょう。
モバイル端末では、OSレベルでマウスイベントの低下が 発生します。OSはジェスチャーが発生したかどうかを識別します。そしてジェスチャーが発生していない場合、マウスイベントとしてそのイベントをWebViewに渡します。タッチイベントを用いると、OS側の遅延なくWebViewにイベントが伝わります。
ただし「click」に相当するタッチイベントが欠如しています。自作することも可能ですし、「taps」というイベントを発生させるライブラリーを用いることも一考です。下記のようなライブラリーが対応しています:Zepto・FastClick・Hammer.js・iScroll
JavaScriptの最適化
本質的に効率的なコードを記述すると、UIスレッドをブロックすることはありません。JavaScriptの最適化に関する記事をお読みいただくといいでしょう。
- Writing Fast, Memory Efficient JavaScript(英語)
- Efficient JavaScript(英語)
- Writing Efficient JavaScript(英語)
アプリを実装する際は、いろいろな最適化手法を考慮する必要があります。友達のプログラマー仲間にコードレビューを依頼するなどして、より良いコードになるよう努力してください。
ネイティブのパフ ォーマンス
ネイティブUIを用いたアプリ開発を行いたいけれども、HTMLでその内容を記述したいとしましょう。実は、PhoneGapがネイティブアプリのサブビューとして利用できます。
この手法はCordovaViewをネイティブアプリのサブビューとして使うものですが、ネイティブコードの開発経験が要求されます。これによりPhoneGapやHTMLのUIを活用しつつ、ネイティブコンポーネントを利用することもできます。
HTMLを用いたカスタムUIの実装はとても簡単です。CordovaViewを用いる方法は、各ネイティブとHTMLの強みを組み合わせることができます。
UIとUXに関して
開発者は優れたユーザー体験を提供することに努力したいと思っているはずです。そのなかで、よくある質問に対する私の回答を紹介します。その質問とは「私のアプリをどうやって、ネイティブアプリのルック&フィールと同一のものにできますか?」というものです。
私のこれに対する回答は、同一にしない、です。
これは、特定のプラットフォームに対して「近い動きをする」アプリを開発してはいけないという意味ではありません。私が伝えたいのは、すべての細かい挙動までOSに合わせる努力をしない方がいいということです。これは、1)とても困難であるため2)OS側での変更が発生すると、あなたのアプリケーションの不一致がより大きなものとなるため、という理由からです。
「不気味の谷」現象という言葉をご存知でしょうか。PhoneGapアプリを開発する際、ネイティブアプリと同一の挙動を作りこむほど、細かい差異が「何か違う」「何かがおかしい」と思うようになってしま います。これがユーザーにとってマイナスのイメージにつながります。
そのため、アプリが固有のUIを実装することを推奨します(App Storeのガイドラインに適合する範囲である必要があります)。たとえば、ルック&フィール、ボタンのスタイル、ナビゲーションなどが該当します。すべての側面からネイティブUXを目指すのではなく、あなたのアプリが提供する固有のブランドをUXとして提供しましょう。利用者はあなたのアプリに対して唯一の評価とし、ネイティブOSとの比較は行わないでしょう。
一方で、もしネイティブのルック&フィールを実現する場合は、CSSスタイルを用いることで完全に再現することができます。
実機でテストする
非常に重要なことです。常に、実機でテストを行いましょう。必ず、です。私は旧機種のテストを行います。それは、旧機種で速度が出せた場合、新機種ではより高速に動作するためです。対象となるすべての端末に対して、テストを行いましょう。私の場合、iOSではiPhone 4(4Sではありません)とiPad 2を対象としています。AndroidではMotorola Atrix、Nexus 7タブレット、Kindle Fire(第一世代)、そしてSamsung Galaxy 10.1(第一世代)でテストを行います。デバイスを借りてほかのプラットフォームに対するテストを行うこともあります。さらに、店舗に出向き展示端末にアプリをインストールして、アプリの見た目を確認することもあります。
翻訳した感想
日本語におけるPhoneGapアプリ開発のノウハウが、英語での情報と比べてまだまだ少ないと実感しています。今後、目にとまったテクニックを随時紹介していきたいと感じました。
記事に触れられている「ネイティブUIの活用」ですが、弊社が提供するPhoneGap開発環境であるMonacaでは実装されています。興味のある方は、こちらのドキュメントを是非ご覧ください。