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

はじめに

昨今のモバイルウェブ環境において、頻繁な更新、不安定なページ遷移、タップイベントの定期的な遅延などは悩みの種の一つにすぎません。デベロッパーはできる限りネイティブに近づけようと試みていますが、ハッキング、リセット、柔軟性に欠けるフレームワークなどによって失敗することがよくあります。

この記事では、モバイル HTML5 ウェブアプリの作成に最低限必要なことについて説明します。主なポイントは、今日のモバイル フレームワークが隠そうとしている隠れた複雑さを取り除くことです。ここでは、最小限のアプローチ(主要な HTML5 API を使用)と基本的な基本事項を確認して、独自のフレームワークの作成や、現在使用しているフレームワークの開発に役立てることができます。

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

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

  1. メモリ割り当てと計算負荷 - ハードウェア アクセラレーションのためだけに DOM 内のすべての要素の合成を行おうとすると、次の作業者に追いやられて大けが追いやられるかもしれません。

  2. 消費電力 — 明らかに、ハードウェアが動作するとバッテリーも消費します。デベロッパー向けの開発では、モバイル ウェブアプリを作成する際に、さまざまなデバイスの制約を考慮しなければなりません。ブラウザ メーカーがより多くのデバイス ハードウェアを利用できるようになるにつれて、この状況はさらに拡大するでしょう。

  3. 競合 - ページ内のすでに高速化されている部分にハードウェア アクセラレーションを適用する際に、動作の不具合が発生しました。そのため、アクセラレーションが重複しているかどうかを知ることは非常に重要です。

ユーザー操作をできるだけスムーズにネイティブに近づけるには、ブラウザを動作させる必要があります。理想的には、モバイル デバイスの CPU で最初のアニメーションをセットアップし、GPU にアニメーション プロセス中に異なるレイヤの合成のみを行うようにするのが理想的です。これが translate3d、scale3d、translateZ であり、アニメーション要素に独自のレイヤを与えるため、デバイスはすべてをスムーズにレンダリングできます。Ariya Hidayat さんは、高速合成や WebKit の仕組みについて詳しく知るために、自身のブログ多くの良い情報を紹介しています。

ページの切り替え

ここでは、モバイルウェブ アプリを開発する際の最も一般的なユーザー インタラクションのアプローチとして、スライド、フリップ、回転エフェクトの 3 つを取り上げます。

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

まず、スライド、フリップ、回転による遷移と、その加速方法について詳細に説明します。各アニメーションに必要なのは、3 行または 4 行の CSS と JavaScript であることに注目してください。

スライディング

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 イーズアウト遷移を使用しているため、通常の 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';
}

遷移がネイティブに感じられるようにキュービック ベジエを試してみたのですが、イーズアウトでうまくいきました。

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

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

回転

次に、このデモで使用されている回転アニメーションを見てみましょう。[連絡先] メニュー オプションをタップすると、表示中のページをいつでも 180 度回転させて反対側を表示できます。この場合も、遷移クラス onclick を割り当てるために必要なのは、数行の CSS と JavaScript だけです。注: 回転遷移は、3D CSS 変換機能がないため、ほとんどのバージョンの Android では正しくレンダリングされません。残念ながら、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 によってコマンドが異なる場合があります。ターミナルを開いて、次のように入力します。

  • $> エクスポート CA_COLOR_OPAQUE=1
  • $> エクスポート 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 の「合成されたレンダリング レイヤの枠線」でも利用できます。

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

近寄りの葉

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

Core Animation 1

ブラウザ ウィンドウをサイズ変更または最大化すると、メモリも拡張されます。

Core Animation 2

これにより、ブラウザのサイズを正しいサイズに変更した場合にのみ、モバイル デバイスでのメモリ使用量を把握できます。iPhone 環境でデバッグやテストを行う場合は、480 x 320 ピクセルにサイズ変更します。これで、ハードウェア アクセラレーションの仕組みとデバッグに必要なものを正確に理解できました。GPU メモリバッファが視覚的に機能していることを実際に確認することは重要です。

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

ここからは、ページとリソースのキャッシュについて、次の段階に進みましょう。JQuery Mobile や類似のフレームワークで使われているアプローチと同様に、AJAX 呼び出しを同時に行うことでページをプリフェッチし、キャッシュに保存します。

モバイルウェブに関するいくつかの主要な問題と、それが必要な理由について説明します。

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

スライド、反転、回転のデモのコードを基に、2 ページ目の追加とリンクの設定から始めます。その後、リンクを解析し、オンザフライで遷移を作成します。

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 になります。アプリケーション キャッシュの範囲外でこれらのページ/マークアップを取得してキャッシュに保存する理由全体は、次のセクションで説明します。

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

仕様上: sandbox 属性を指定すると、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 の「ランダムに動作しなくなる」に関して大規模なテストは行っていません。しかし、この問題に影響するすべてのプラットフォームを見ることは非常に興味深いものになります。また、どちらのアプローチがよりパフォーマンスが高いかも興味深いです。これについても、双方から意見を聞いています。

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

ウェブアプリをバッファリング(予測キャッシュ)できるようになったので、アプリをよりスマートにするための適切な接続検出機能を提供する必要があります。こうした状況のなかで、モバイルアプリ開発ではオンライン/オフライン モードや接続速度が極めて重要です。「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)のリソースをフェッチするよう指示しただけです。

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

エッジ同期

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

Wi-Fi 非同期

これにより、低速または高速の接続に基づいて、少なくともなんらかの方法でユーザー エクスペリエンスを調整できます。決して、これが最終的な解決策というわけではありません。もう一つの対処は、アプリがバックグラウンドでリンクのページを取得している間に(接続速度が遅い場合)リンクがクリックされたときに、読み込みモーダルをスローすることです。 ここでの大きなポイントは、遅延を低減しながら、最新かつ優れた HTML5 が提供するユーザーの接続機能を最大限に活用できるようにすることです。 ネットワーク検出のデモは、こちらでご覧いただけます

まとめ

モバイル HTML5 アプリへの道のりはまだ始まったばかりです。ここまでで、HTML5 のみで構築されたモバイルの「フレームワーク」の非常にシンプルで基本的な基盤がわかりました。それは、その枠組みです。開発者にとっては、ラッパーでマスクするのではなく、これらの機能を中核部分で扱い、対処することが重要だと思います。