モバイルでのパフォーマンスを最適化する HTML5 の手法

はじめに

更新の遅延、ページ遷移の途切れ、タップイベントの定期的な遅延は、今日のモバイルウェブ環境で発生する問題のほんの一部にすぎません。デベロッパーはネイティブにできるだけ近づけようとしていますが、ハック、リセット、堅固なフレームワークによって行き詰まってしまうことがよくあります。

この記事では、モバイル HTML5 ウェブアプリを作成するために必要な最小限の要件について説明します。主なポイントは、今日のモバイル フレームワークが隠そうとしている複雑さを明らかにすることです。コア HTML5 API を使用したミニマルなアプローチと基本的な基礎を学び、独自のフレームワークを作成したり、現在使用しているフレームワークに貢献したりできるようになります。

ハードウェア アクセラレーション

通常、GPU は詳細な 3D モデリングや CAD 図を処理しますが、ここではプリミティブな描画(div、背景、ドロップ シャドウ付きのテキスト、画像など)を GPU を介してスムーズに表示し、アニメーション化します。残念なことに、ほとんどのフロントエンド デベロッパーは、セマンティクスを気にすることなく、このアニメーション プロセスをサードパーティ フレームワークに任せています。しかし、これらのコア CSS3 機能をマスクすべきでしょうか?これを重視することが重要な理由をいくつかご紹介しましょう。

  1. メモリの割り当てと計算負荷 - ハードウェア アクセラレーションのためだけに DOM ですべての要素を合成してしまうと、次にコードに取り組む人に追いかけられ、大きな打撃を受ける可能性があります。

  2. 消費電力 - ハードウェアが動作すると、バッテリーも使用されます。モバイル向けに開発する場合、デベロッパーはモバイル ウェブアプリの作成時に、さまざまなデバイスの制約を考慮する必要があります。ブラウザ メーカーが次々とデバイス ハードウェアへのアクセスを可能にするにつれ、これはいっそう普及するでしょう。

  3. 競合 - すでにアクセラレーションが適用されているページの部分にハードウェア アクセラレーションを適用すると、グリッチが発生します。そのため、加速度が重複しているかどうかを把握することが非常に重要です。

ユーザー操作をスムーズにし、ネイティブにできるだけ近づけるには、ブラウザを活用する必要があります。理想的には、モバイル デバイスの CPU が初期アニメーションを設定し、GPU がアニメーション プロセス中にさまざまなレイヤの合成のみを行うようにします。これが translate3d、scale3d、translateZ の役割です。アニメーション要素に独自のレイヤを割り当てることで、デバイスですべてをスムーズにレンダリングできます。高速合成と WebKit の仕組みについて詳しくは、Ariya Hidayat のブログ多くの有益な情報をご確認ください。

ページの遷移

モバイルウェブ アプリの開発でよく使用されるユーザー インタラクション アプローチのうち、スライド効果、反転効果、回転効果の 3 つを見ていきましょう。

このコードの動作は、http://slidfast.appspot.com/slide-flip-rotate.html で確認できます(注: このデモはモバイル デバイス用に作成されているため、エミュレータを起動するか、スマートフォンまたはタブレットを使用するか、ブラウザ ウィンドウのサイズを 1,024 ピクセル以下に縮小してください)。

まず、スライド、フリップ、回転の遷移と、それらの遷移を高速化する方法について説明します。各アニメーションに必要な CSS と JavaScript は、3 ~ 4 行のみです。

スライド

この 3 つの移行方法のうち最も一般的なものは、モバイル アプリのネイティブな感覚を再現するスライド形式のページ移行です。スライド遷移が呼び出され、新しいコンテンツ領域がビューポートに表示されます。

スライド エフェクトの場合、まずマークアップを宣言します。

<div id="home-page" class="page">
 
<h1>Home Page</h1>
</div>

<div id="products-page" class="page stage-right">
 
<h1>Products Page</h1>
</div>

<div id="about-page" class="page stage-left">
 
<h1>About Page</h1>
</div>

左側または右側にステージング ページがある点に注目してください。基本的にはどの方向でも構いませんが、これが最も一般的です。

これで、わずか数行の CSS で、アニメーションとハードウェア アクセラレーションが完成しました。実際のアニメーションは、ページ div 要素のクラスを入れ替えたときに発生します。

.page {
 
position: absolute;
 
width: 100%;
 
height: 100%;
 
/*activate the GPU for compositing each page */
 
-webkit-transform: translate3d(0, 0, 0);
}

translate3d(0,0,0) は「万能薬」アプローチと呼ばれます。

ユーザーがナビゲーション要素をクリックすると、次の JavaScript が実行され、クラスが入れ替わります。サードパーティのフレームワークは使用されておらず、JavaScript のみで作成されています。;)

function getElement(id) {
 
return document.getElementById(id);
}

function slideTo(id) {
 
//1.) the page we are bringing into focus dictates how
 
// the current page will exit. So let's see what classes
 
// our incoming page is using. We know it will have stage[right|left|etc...]
 
var classes = getElement(id).className.split(' ');

 
//2.) decide if the incoming page is assigned to right or left
 
// (-1 if no match)
 
var stageType = classes.indexOf('stage-left');

 
//3.) on initial page load focusPage is null, so we need
 
// to set the default page which we're currently seeing.
 
if (FOCUS_PAGE == null) {
   
// use home page
    FOCUS_PAGE
= getElement('home-page');
 
}

 
//4.) decide how this focused page should exit.
 
if (stageType > 0) {
    FOCUS_PAGE
.className = 'page transition stage-right';
 
} else {
    FOCUS_PAGE
.className = 'page transition stage-left';
 
}

 
//5. refresh/set the global variable
  FOCUS_PAGE
= getElement(id);

 
//6. Bring in the new page.
  FOCUS_PAGE
.className = 'page transition stage-center';
}

stage-left または stage-rightstage-center になり、ページがビューポートの中央にスライドします。重い処理はすべて CSS3 に依存しています。

.stage-left {
 
left: -480px;
}

.stage-right {
 
left: 480px;
}

.stage-center {
 
top: 0;
 
left: 0;
}

次に、モバイル デバイスの検出と向きを処理する CSS を見てみましょう。すべてのデバイスと解像度に対応できます(メディアクエリの解像度をご覧ください)。このデモでは、モバイル デバイスの縦向きと横向きのほとんどのビューに対応するために、いくつかの簡単な例を使用しています。これは、デバイスごとにハードウェア アクセラレーションを適用する場合にも便利です。たとえば、PC 版の WebKit では、(2D か 3D かにかかわらず)変換されたすべての要素が高速化されるため、メディアクエリを作成して、そのレベルでアクセラレーションを除外するのが合理的です。Android Froyo 2.2 以降では、ハードウェア アクセラレーションのトリックによって速度が向上することはありません。すべての合成はソフトウェア内で行われます。

/* iOS/android phone landscape screen width*/
@media screen and (max-device-width: 480px) and (orientation:landscape) {
 
.stage-left {
   
left: -480px;
 
}

 
.stage-right {
   
left: 480px;
 
}

 
.page {
   
width: 480px;
 
}
}

フリッピング

モバイル デバイスでは、ページを実際にスワイプして閉じることを「めくる」といいます。ここでは、簡単な JavaScript を使用して、iOS デバイスと Android デバイス(WebKit ベース)でこのイベントを処理します。

実際の動作はこちら(http://slidfast.appspot.com/slide-flip-rotate.html)でご確認ください。

タッチイベントや遷移を処理する場合、まず必要なのは、要素の現在の位置のハンドルを取得することです。WebKitCSSMatrix の詳細については、こちらのドキュメントをご覧ください。

function pageMove(event) {
 
// get position after transform
 
var curTransform = new WebKitCSSMatrix(window.getComputedStyle(page).webkitTransform);
 
var pagePosition = curTransform.m41;
}

ページめくりに CSS3 ease-out 遷移を使用しているため、通常の element.offsetLeft は機能しません。

次に、ユーザーが反転している方向を特定し、イベント(ページ ナビゲーション)が発生するしきい値を設定します。

if (pagePosition >= 0) {
 
//moving current page to the right
 
//so means we're flipping backwards
   
if ((pagePosition > pageFlipThreshold) || (swipeTime < swipeThreshold)) {
     
//user wants to go backward
     slideDirection
= 'right';
   
} else {
     slideDirection
= null;
   
}
} else {
 
//current page is sliding to the left
 
if ((swipeTime < swipeThreshold) || (pagePosition < pageFlipThreshold)) {
   
//user wants to go forward
    slideDirection
= 'left';
 
} else {
    slideDirection
= null;
 
}
}

また、swipeTime をミリ秒単位で測定していることにも注目してください。これにより、ユーザーが画面をすばやくスワイプしてページをめくった場合に、ナビゲーション イベントを発生させることができます。

ページを配置し、指が画面に触れている間アニメーションをネイティブに見せるため、各イベントの発生後に CSS3 遷移を使用しています。

function positionPage(end) {
  page
.style.webkitTransform = 'translate3d('+ currentPos + 'px, 0, 0)';
 
if (end) {
    page
.style.WebkitTransition = 'all .4s ease-out';
   
//page.style.WebkitTransition = 'all .4s cubic-bezier(0,.58,.58,1)'
 
} else {
    page
.style.WebkitTransition = 'all .2s ease-out';
 
}
  page
.style.WebkitUserSelect = 'none';
}

3 次ベジェをいろいろ試して移行にネイティブな雰囲気を出してみましたが、ease-out がうまくいきました。

最後に、ナビゲーションを実行するには、前回のデモで使用した、前に定義した slideTo() メソッドを呼び出す必要があります。

track.ontouchend = function(event) {
  pageMove
(event);
 
if (slideDirection == 'left') {
    slideTo
('products-page');
 
} else if (slideDirection == 'right') {
    slideTo
('home-page');
 
}
}

回転

次に、このデモで使用されている回転アニメーションを見てみましょう。表示しているページは、いつでも [連絡先] メニュー オプションをタップして 180 度回転させ、裏面を表示できます。遷移クラス onclick を割り当てるには、CSS と JavaScript を数行記述するだけで済みます。注: ほとんどのバージョンの Android では、3D CSS 変換機能がないため、回転遷移が正しくレンダリングされません。残念ながら、Android は裏返しを無視するのではなく、ページをめくるのではなく回転させることで、ページを「カートホイール」から外します。サポートが改善されるまで、この移行は慎重に使用することをおすすめします。

マークアップ(前面と背面の基本コンセプト):

<div id="front" class="normal">
...
</div>
<div id="back" class="flipped">
   
<div id="contact-page" class="page">
       
<h1>Contact Page</h1>
   
</div>
</div>

JavaScript:

function flip(id) {
 
// get a handle on the flippable region
 
var front = getElement('front');
 
var back = getElement('back');

 
// again, just a simple way to see what the state is
 
var classes = front.className.split(' ');
 
var flipped = classes.indexOf('flipped');

 
if (flipped >= 0) {
   
// already flipped, so return to original
    front
.className = 'normal';
    back
.className = 'flipped';
    FLIPPED
= false;
 
} else {
   
// do the flip
    front
.className = 'flipped';
    back
.className = 'normal';
    FLIPPED
= true;
 
}
}

CSS:

/*----------------------------flip transition */
#back,
#front {
 
position: absolute;
 
width: 100%;
 
height: 100%;
 
-webkit-backface-visibility: hidden;
 
-webkit-transition-duration: .5s;
 
-webkit-transform-style: preserve-3d;
}

.normal {
 
-webkit-transform: rotateY(0deg);
}

.flipped {
 
-webkit-user-select: element;
 
-webkit-transform: rotateY(180deg);
}

ハードウェア アクセラレーションのデバッグ

基本的な切り替えについて説明したので、次は、切り替えの仕組みと合成の仕組みについて説明します。

この魔法のようなデバッグ セッションを実現するには、いくつかのブラウザと任意の IDE を起動しましょう。まず、コマンドラインから Safari を起動して、デバッグ環境変数を使用します。私は Mac を使用していますが、コマンドは OS によって異なる場合があります。ターミナルを開き、次のように入力します。

  • $> export CA_COLOR_OPAQUE=1
  • $> export CA_LOG_MEMORY_USAGE=1
  • $> /Applications/Safari.app/Contents/MacOS/Safari

これにより、いくつかのデバッグ ヘルパーとともに Safari が起動します。CA_COLOR_OPAQUE は、実際に合成または高速化されている要素を示します。CA_LOG_MEMORY_USAGE は、描画オペレーションをバッキング ストアに送信する際に使用しているメモリ量を示します。これにより、モバイル デバイスにどれだけ負荷がかかっているかを正確に把握できます。また、GPU の使用によってターゲット デバイスのバッテリーがどれだけ消耗しているかを把握できる可能性があります。

次に、Chrome を起動して、適切なフレーム/秒(FPS)情報を確認してみましょう。

  1. Google Chrome ウェブブラウザを開きます。
  2. URL バーに「about:flags」と入力します。
  3. いくつかの項目を下にスクロールし、FPS カウンタの [有効にする] をクリックします。

改良版の Chrome でこちらのページを表示すると、左上に赤い FPS カウンタが表示されます。

Chrome FPS

ハードウェア アクセラレーションがオンになっているかどうかは、この方法で確認できます。また、アニメーションの実行方法や、リーク(停止すべきアニメーションが連続して実行されている)があるかどうかも把握できます。

ハードウェア アクセラレーションを実際に視覚化するもう 1 つの方法は、Safari で同じページを開くことです(前述の環境変数を使用)。高速化された DOM 要素はすべて赤色が付いています。これにより、レイヤごとに何が合成されているかを正確に把握できます。白のナビゲーションは高速化されていないため、赤色ではありません。

合成された連絡先

Chrome では、about:flags の [Composited render layer borders] で同様の設定を利用できます。

合成レイヤを確認するもう 1 つの方法は、このモッドを適用した状態で WebKit の落ち葉のデモを表示することです。

落ち葉

最後に、アプリケーションのグラフィック ハードウェアのパフォーマンスを正確に把握するために、メモリの消費状況を確認しましょう。ここでは、1.38 MB の描画命令を Mac OS の CoreAnimation バッファにプッシュしていることがわかります。Core Animation のメモリバッファは、OpenGL ES と GPU で共有され、画面に表示される最終的なピクセルを作成します。

Coreanimation 1

単にブラウザ ウィンドウのサイズを変更したり、最大化したりすると、メモリも大きくなります。

Coreanimation 2

ブラウザのサイズを正しいサイズに変更した場合にのみ、モバイル デバイスでメモリがどのように消費されているかを確認できます。iPhone 環境のデバッグまたはテストを行っていた場合は、サイズを 480 x 320 ピクセルに変更します。これで、ハードウェア アクセラレーションの仕組みとデバッグに必要なことを正確に理解できました。説明を読むだけではわかりにくいかもしれませんが、GPU メモリ バッファが実際に動作している様子を視覚的に確認すると、理解が深まります。

舞台裏: 取得とキャッシュ保存

それでは、ページとリソースのキャッシュを次のレベルに引き上げましょう。JQuery Mobile や同様のフレームワークが使用するアプローチと同様に、AJAX の同時呼び出しでページをプリフェッチし、キャッシュに保存します。

では、モバイルウェブの主要な問題をいくつか取り上げて、その理由を見ていきましょう。

  • フェッチ: ページをプリフェッチすることで、ユーザーはアプリをオフラインにできます。また、ナビゲーション アクションの間に待機する必要がなくなります。ただし、デバイスがオンラインになったときにデバイスの帯域幅を圧迫しないように、この機能は控えめに使用する必要があります。
  • キャッシュ保存: 次に、これらのページの取得とキャッシュ保存には、同時実行または非同期のアプローチが必要です。また、デバイス間で広くサポートされているため、localStorage を使用する必要がありますが、これは非同期ではありません。
  • AJAX でレスポンスを解析する: innerHTML() を使用して AJAX レスポンスを DOM に挿入するのは危険です(また、信頼性に欠けます)。代わりに、AJAX レスポンスの挿入と同時呼び出しの処理に信頼できるメカニズムを使用しています。また、xhr.responseText の解析に HTML5 の新機能を活用しています。

スライド、フリップ、回転のデモのコードから、まずいくつかのセカンダリ ページを追加してリンクを設定します。リンクを解析し、その場で遷移を作成します。

iPhone のホーム画面

取得とキャッシュのデモはこちらでご覧ください。

ご覧のように、ここではセマンティック マークアップを利用しています。別のページへのリンクのみです。子ページは、親と同じノード/クラス構造に従います。さらに一歩進めて、「page」ノードなどに data-* 属性を使用することもできます。以下は、別の HTML ファイル(/demo2/home-detail.html)にある詳細ページ(子)です。このページは、アプリの読み込み時に読み込まれ、キャッシュに保存され、遷移用に設定されます。

<div id="home-page" class="page">
 
<h1>Home Page</h1>
 
<a href="demo2/home-detail.html" class="fetch">Find out more about the home page!</a>
</div>

では、JavaScript を見てみましょう。わかりやすくするため、ヘルパーや最適化はコードに含めていません。ここでは、指定された DOM ノードの配列をループして、取得してキャッシュに保存するリンクを探します。注 - このデモでは、このメソッド fetchAndCache() はページの読み込み時に呼び出されます。次のセクションで、ネットワーク接続を検出して呼び出すタイミングを決定するときに、この関数を変更します。

var fetchAndCache = function() {
 
// iterate through all nodes in this DOM to find all mobile pages we care about
 
var pages = document.getElementsByClassName('page');

 
for (var i = 0; i < pages.length; i++) {
   
// find all links
   
var pageLinks = pages[i].getElementsByTagName('a');

   
for (var j = 0; j < pageLinks.length; j++) {
     
var link = pageLinks[j];

     
if (link.hasAttribute('href') &amp;&amp;
     
//'#' in the href tells us that this page is already loaded in the DOM - and
     
// that it links to a mobile transition/page
         
!(/[\#]/g).test(link.href) &amp;&amp;
       
//check for an explicit class name setting to fetch this link
       
(link.className.indexOf('fetch') >= 0))  {
         
//fetch each url concurrently
         
var ai = new ajax(link,function(text,url){
             
//insert the new mobile page into the DOM
             insertPages
(text,url);
         
});
         ai
.doGet();
     
}
   
}
 
}
};

Google は「AJAX」オブジェクトを使用して、適切な非同期ポスト処理を実現しています。AJAX 呼び出し内で localStorage を使用する方法について詳しくは、HTML5 オフラインでオフグリッドで作業するをご覧ください。この例では、各リクエストでキャッシュを使用する基本的な方法と、サーバーが正常な(200)レスポンス以外のレスポンスを返した場合にキャッシュに保存されたオブジェクトを提供する方法を示します。

function processRequest () {
 
if (req.readyState == 4) {
   
if (req.status == 200) {
     
if (supports_local_storage()) {
        localStorage
[url] = req.responseText;
     
}
     
if (callback) callback(req.responseText,url);
   
} else {
     
// There is an error of some kind, use our cached copy (if available).
     
if (!!localStorage[url]) {
       
// We have some data cached, return that to the callback.
        callback
(localStorage[url],url);
       
return;
     
}
   
}
 
}
}

残念ながら、localStorage は文字エンコードに UTF-16 を使用するため、1 バイトが 2 バイトとして保存され、ストレージの上限は 5 MB から合計 2.6 MB に減少します。これらのページやマークアップをアプリケーション キャッシュ スコープの外部で取得してキャッシュに保存する理由については、次のセクションで説明します。

最近の HTML5 での iframe 要素の進歩により、AJAX 呼び出しから返された responseText を簡単かつ効果的に解析できるようになりました。スクリプトタグなどを削除する 3,000 行の JavaScript パーサーや正規表現はたくさんあります。でも、ブラウザには最も得意なことを任せてはいかがでしょうか。この例では、一時的な非表示 iframe に responseText を書き込みます。Google は、スクリプトを無効にし、多くのセキュリティ機能を提供する HTML5 の「サンドボックス」属性を使用しています。

仕様から: サンドボックス属性を指定すると、iframe によってホストされるコンテンツに対して一連の追加制限が有効になります。その値には、スペースで区切られた一意のトークンを順序なしのセットで指定します。ASCII の大文字と小文字は区別されません。指定できる値は、allow-forms、allow-same-origin、allow-scripts、allow-top-navigation です。この属性が設定されている場合、コンテンツは固有のオリジンからのものとして扱われ、フォームとスクリプトは無効になり、リンクが他のブラウジング コンテキストをターゲットに設定できなくなり、プラグインが無効になります。

var insertPages = function(text, originalLink) {
 
var frame = getFrame();
 
//write the ajax response text to the frame and let
 
//the browser do the work
  frame
.write(text);

 
//now we have a DOM to work with
 
var incomingPages = frame.getElementsByClassName('page');

 
var pageCount = incomingPages.length;
 
for (var i = 0; i < pageCount; i++) {
   
//the new page will always be at index 0 because
   
//the last one just got popped off the stack with appendChild (below)
   
var newPage = incomingPages[0];

   
//stage the new pages to the left by default
    newPage
.className = 'page stage-left';

   
//find out where to insert
   
var location = newPage.parentNode.id == 'back' ? 'back' : 'front';

   
try {
     
// mobile safari will not allow nodes to be transferred from one DOM to another so
     
// we must use adoptNode()
      document
.getElementById(location).appendChild(document.adoptNode(newPage));
   
} catch(e) {
     
// todo graceful degradation?
   
}
 
}
};

Safari では、ドキュメント間でノードを暗黙的に移動することを正しく拒否します。新しい子ノードが別のドキュメントで作成されている場合、エラーが発生します。ここでは adoptNode を使用しており、問題はありません。

では、なぜ iframe を使用するのでしょうか。innerHTML を使ってはいけません。innerHTML は HTML5 仕様の一部になりましたが、未確認の領域にサーバーからのレスポンスを挿入するのは危険な行為です。この記事を書いている時点では、innerHTML 以外を使用しているは見つかりませんでした。JQuery では、例外の場合にのみ追加のフォールバックを使用して、コアでこれを使用しています。JQuery Mobile でもこれを使用しています。ただし、innerHTML の「動作が突然停止する」問題については、大規模なテストは行っていません。この問題が影響するすべてのプラットフォームを把握できれば、非常に興味深い結果になると思います。また、どちらのアプローチがパフォーマンスが高いかも興味深い点です。この点については、両側から主張が聞かれます。

ネットワーク タイプの検出、処理、プロファイリング

ウェブアプリをバッファリング(または予測キャッシュ)できるようになりました。次は、アプリをよりスマートにする適切な接続検出機能を提供する必要があります。モバイルアプリ開発において特に重要なのが、オンライン/オフライン モードや接続速度です。The Network Information API を入力します。プレゼンテーションでこの機能を説明するたびに、聴衆の中から「この機能は何に使うのですか?」という質問が寄せられます。そこで、非常にスマートなモバイルウェブアプリを設定できる方法をご紹介します。

まず、退屈な常識的なシナリオをご紹介します。高速列車でモバイル デバイスからウェブを操作しているときに、ネットワークが断続的に切断される可能性があります。また、地域によってサポートされている転送速度が異なる場合があります(一部の都市部では HSPA や 3G が利用可能かもしれませんが、遠隔地でははるかに遅い 2G テクノロジーがサポートされている場合があります)。次のコードは、ほとんどの接続シナリオに対応しています。

次のコードは次の処理を行います。

  • applicationCache によるオフライン アクセス。
  • ブックマークされているかどうか、オフラインかどうかを検出します。
  • オフラインからオンラインに切り替えられたときや、その逆の切り替えが検出されます。
  • 低速な接続を検出し、ネットワーク タイプに基づいてコンテンツを取得します。

これらの機能はすべて、ごくわずかなコードで実現できます。まず、イベントと読み込みシナリオを検出します。

window.addEventListener('load', function(e) {
 
if (navigator.onLine) {
 
// new page load
  processOnline
();
 
} else {
   
// the app is probably already cached and (maybe) bookmarked...
   processOffline
();
 
}
}, false);

window
.addEventListener("offline", function(e) {
 
// we just lost our connection and entered offline mode, disable eternal link
  processOffline
(e.type);
}, false);

window
.addEventListener("online", function(e) {
 
// just came back online, enable links
  processOnline
(e.type);
}, false);

上記の EventListener では、イベントから呼び出されているか、実際のページ リクエストまたは更新から呼び出されているかをコードに伝える必要があります。その主な理由は、オンライン モードとオフライン モードを切り替えても body の onload イベントが発生しないためです。

次に、ononline イベントまたは onload イベントの簡単なチェックがあります。このコードは、オフラインからオンラインに切り替えたときに無効なリンクをリセットしますが、このアプリがより高度なものであれば、コンテンツの取得を再開するロジックや、断続的な接続の UX を処理するロジックを挿入できます。

function processOnline(eventType) {

  setupApp
();
  checkAppCache
();

 
// reset our once disabled offline links
 
if (eventType) {
   
for (var i = 0; i < disabledLinks.length; i++) {
      disabledLinks
[i].onclick = null;
   
}
 
}
}

processOffline() についても同様です。ここでは、オフライン モードのアプリを操作し、バックグラウンドで進行していたトランザクションを復元しようとします。以下のコードは、すべての外部リンクを探し出して無効にします。これにより、ユーザーはオフライン アプリに永遠に閉じ込められます。ムハハハ!

function processOffline() {
  setupApp
();

 
// disable external links until we come back - setting the bounds of app
  disabledLinks
= getUnconvertedLinks(document);

 
// helper for onlcick below
 
var onclickHelper = function(e) {
   
return function(f) {
      alert
('This app is currently offline and cannot access the hotness');return false;
   
}
 
};

 
for (var i = 0; i < disabledLinks.length; i++) {
   
if (disabledLinks[i].onclick == null) {
     
//alert user we're not online
      disabledLinks
[i].onclick = onclickHelper(disabledLinks[i].href);

   
}
 
}
}

では、本題に入りましょう。アプリが接続状態を認識できるようになったので、オンラインのときに接続の種類を確認して、それに応じて調整することもできます。各接続のコメントに、北米の一般的なプロバイダのダウンロードとレイテンシを示します。

function setupApp(){
 
// create a custom object if navigator.connection isn't available
 
var connection = navigator.connection || {'type':'0'};
 
if (connection.type == 2 || connection.type == 1) {
     
//wifi/ethernet
     
//Coffee Wifi latency: ~75ms-200ms
     
//Home Wifi latency: ~25-35ms
     
//Coffee Wifi DL speed: ~550kbps-650kbps
     
//Home Wifi DL speed: ~1000kbps-2000kbps
      fetchAndCache
(true);
 
} else if (connection.type == 3) {
 
//edge
     
//ATT Edge latency: ~400-600ms
     
//ATT Edge DL speed: ~2-10kbps
      fetchAndCache
(false);
 
} else if (connection.type == 2) {
     
//3g
     
//ATT 3G latency: ~400ms
     
//Verizon 3G latency: ~150-250ms
     
//ATT 3G DL speed: ~60-100kbps
     
//Verizon 3G DL speed: ~20-70kbps
      fetchAndCache
(false);
 
} else {
 
//unknown
      fetchAndCache
(true);
 
}
}

fetchAndCache プロセスにはさまざまな調整を加えることができますが、ここでは、特定の接続についてリソースを非同期(true)または同期(false)に取得するよう指示しただけです。

エッジ(同期)リクエストのタイムライン

エッジ同期

Wi-Fi(非同期)リクエストのタイムライン

WIFI 非同期

これにより、接続の速度に応じてユーザー エクスペリエンスを調整する方法が少なくともいくつか用意できます。これは決して万能のソリューションではありません。別の ToDo としては、リンクがクリックされたときに(接続が遅い場合)、アプリがそのリンクのページをバックグラウンドでフェッチしている間に読み込みモーダルを表示することも考えられます。ここで重要なのは、最新の HTML5 でユーザーの接続の機能を最大限に活用しながら、レイテンシを短縮することです。ネットワーク検出のデモはこちらをご覧ください。

まとめ

モバイル HTML5 アプリの道のりは始まったばかりです。これで、HTML5 とそのサポート技術のみで構築されたモバイル「フレームワーク」の非常にシンプルで基本的な基盤が完成しました。デベロッパーは、ラッパーで隠さずに、これらの機能をコアで扱い、対処することが重要だと考えています。