HTML5 サイトのモバイル化

はじめに

昨今、モバイルウェブ向けの開発が大きな話題になっています。今年は、初めてスマートフォンの販売台数が PC を上回った年です。モバイル デバイスを使用してウェブを閲覧するユーザーが増えているため、デベロッパーにとって、モバイル ブラウザ向けにサイトを最適化することが重要になっています。

多くのデベロッパーにとって、「モバイル」の戦場はまだ未知の領域です。多くの人が、既存のレガシーサイトを運営していて、モバイル ユーザーを完全に無視しているという人も多いでしょう。代わりに、このサイトは主にパソコンでのブラウジング用に設計されており、モバイル ブラウザでは表示が不十分です。このサイト(html5rocks.com)も例外ではありません。リリース当初は、サイトのモバイル版にほとんど手をかけていませんでした。

モバイル対応の html5rocks.com を作成する

演習として、html5rocks(既存の HTML5 サイト)をモバイル対応のバージョンに拡張してみることにしました。私の主な関心事はスマートフォンをターゲットに 行うために必要な最低限の作業量ですこの演習の目的は、まったく新しいモバイルサイトを作成して 2 つのコードベースを維持することではありません。時間がかかり、時間の無駄になるため、サイトの構造(マークアップ)はすでに定義しています。見た目と操作性(CSS)を改善しました。コア機能(JS)は存在していました。多くのサイトが同じ状況にあります。

この記事では、Android デバイスと iOS デバイス向けに最適化された html5rocks のモバイル版がどのように作成されたかについて説明します。これらの OS のいずれかをサポートするデバイスで html5rocks.com を読み込むだけで、違いを確認できます。m.html5rocks.com などのリダイレクトはありません。html5rocks はそのまま使用できますが、モバイル デバイスで美しく機能するメリットもあります。

パソコン版 html5rocks.com モバイル html5rocks.com
パソコン(左)とモバイル(右)の html5rocks.com

CSS メディアクエリ

HTML4 と CSS2 では、以前からメディア依存のスタイルシートがサポートされてきました。例:

<link rel="stylesheet" media="print" href="printer.css">

は、印刷デバイスをターゲットにし、印刷時にページ コンテンツに特定のスタイルを適用します。CSS3 では、メディアタイプの概念をさらに一歩進め、メディアクエリによって機能を強化しています。メディアクエリを使用すると、スタイルシートにより正確にラベル付けできるため、メディアタイプの有用性が拡張されます。これにより、コンテンツ自体を変更しなくても、コンテンツの表示を特定の範囲の出力デバイス向けにカスタマイズできます。これは、変更が必要な既存のレイアウトに最適です。

外部スタイルシートの media 属性でメディアクエリを使用して、画面幅、デバイス幅、向きなどをターゲットに設定できます。一覧については、W3C メディアクエリ仕様をご覧ください。

画面サイズをターゲットに設定する

次の例では、phone.css はブラウザが「ハンドヘルド」と見なされるデバイス、または画面の幅が 320 ピクセル以下のデバイスに適用されます。

 <link rel='stylesheet'
  media='handheld, only screen and (max-device-width: 320px)' href='phone.css'>

メディアクエリの前に「only」キーワードを付けると、CSS3 に準拠していないブラウザでルールが無視されます。

次の例では、641 ~ 800 ピクセルの画面サイズがターゲットとなります。

 <link rel='stylesheet'
  media='only screen and (min-width: 641px) and (max-width: 800px)' href='ipad.css'>

メディアクエリは、インライン <style> タグ内に配置することもできます。次のターゲティングは、縦向きの場合に all メディアタイプをターゲットにします。

 <style>
  @media only all and (orientation: portrait) { ... }
 </style>

media="handheld"

ここで少し立ち止まって、media="handheld" について説明します。実際、Android と iOS では media="handheld" は無視されます。media="screen" をターゲットとするスタイルシートによって提供される高品質のコンテンツがユーザーに提供されなくなること、デベロッパーが品質の低い media="handheld" バージョンを維持する可能性が低くなることが主張されています。そのため、ほとんどの最新のスマートフォン ブラウザは、「フルウェブ」というモットーの一環として、ハンドヘルド スタイルシートを無視します。

この機能を使用してモバイル デバイスをターゲットに設定するのが理想的ですが、さまざまなブラウザで実装方法が異なります。

  • ハンドヘルド スタイルシートのみを読み取るものもあります。
  • ハンドヘルド スタイルシートがある場合はそのスタイルシートのみを読み取り、ない場合はデフォルトで画面スタイルシートを読み取るものもあります。
  • ハンドヘルド スタイルシートと画面スタイルシートの両方を読み取るものもあります。
  • 一部は画面スタイルシートのみを読み取ります。

Opera Mini では media="handheld" が無視されません。Windows Mobile で media="handheld" を認識させるには、画面スタイルシートの media 属性値を大文字にします。

 <!-- media="handheld" trick for Windows Mobile -->
 <link rel="stylesheet" href="screen.css" media="Screen">
 <link rel="stylesheet" href="mobile.css" media="handheld">

html5rocks でメディアクエリを使用する方法

メディアクエリは、モバイル html5rocks 全体で頻繁に使用されています。Django テンプレート マークアップに大幅な変更を加えなくてもレイアウトを微調整できるため、非常に便利です。また、さまざまなブラウザでのサポートも充実しています。

各ページの <head> には、次のスタイルシートが表示されます。

 <link rel='stylesheet'
  media='all' href='/static/css/base.min.css' />
 <link rel='stylesheet'
  media='only screen and (max-width: 800px)' href='/static/css/mobile.min.css' />

base.css はこれまで html5rocks.com のメインのルック&フィールを定義してきましたが、現在は画面幅が 800 ピクセル未満の場合に新しいスタイル(mobile.css)を適用しています。このメディアクエリは、スマートフォン(約 320 ピクセル)と iPad(約 768 ピクセル)をカバーしています。効果: モバイルでの表示を改善するため、base.css のスタイルを(必要に応じてのみ)段階的にオーバーライドしています。

mobile.css によって適用されるスタイルの変更の一部を以下に示します。

  • サイト全体で余計な空白やパディングを減らす。画面が小さいということは、スペースが限られているということです。
  • :hover 状態を削除します。タッチデバイスでは表示されません。
  • レイアウトを単一列に調整します。詳しくは後で説明します。
  • サイトのメインコンテナ div の周囲の box-shadow を削除しました。大きなボックス シャドウがあると、ページのパフォーマンスが低下します。
  • CSS フレックスボックス モデル box-ordinal-group を使用して、ホームページの各セクションの順序を変更しました。ホームページでは [主要な HTML5 機能グループ別に学習] が [チュートリアル] セクションの前に表示されますが、モバイル版では後ろに表示されます。この順序はモバイルでは理にかなっているので、マークアップの変更は必要ありませんでした。CSS Flexbox 最高!
  • opacity の変更を削除します。アルファ値を変更すると、モバイルのパフォーマンスが低下します。

モバイル メタタグ

モバイル WebKit は、特定のデバイスでユーザーのブラウジング エクスペリエンスを向上させるいくつかの機能をサポートしています。

ビューポートの設定

最初のメタ設定(最もよく使用する設定)は、viewport プロパティです。ビューポートを設定すると、コンテンツをデバイスの画面にどのように収めるかをブラウザに指示し、サイトがモバイル向けに最適化されていることをブラウザに通知します。例:

 <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">

は、ビューポートをデバイスの幅に設定し、初期スケールを 1 に設定するようブラウザに指示します。この例では、ズームも許可されています。これは、ウェブサイトでは望ましいかもしれませんが、ウェブアプリでは望ましくありません。user-scalable=no でズームを防止するか、スケーリングを特定のレベルに制限できます。

 <meta name=viewport
  content="width=device-width, initial-scale=1.0, minimum-scale=0.5 maximum-scale=1.0">

Android では、デベロッパーがサイトが開発された画面解像度を指定できるようにすることで、viewport meta タグを拡張しています。

 <meta name="viewport" content="target-densitydpi=device-dpi">

target-densitydpi の想定される値は、device-dpihigh-dpimedium-dpilow-dpi です。

画面の密度に応じてウェブページを変更する場合は、-webkit-device-pixel-ratio CSS メディアクエリまたは JavaScript の window.devicePixelRatio プロパティを使用し、target-densitydpi メタプロパティを device-dpi に設定します。これにより、Android がウェブページでのスケーリングを行わせなくなり、CSS や JavaScript を介して各密度に必要な調整ができるようになります。

デバイス解像度のターゲット設定について詳しくは、Android の WebView のドキュメントをご覧ください。

全画面ブラウジング

他に 2 つのメタ値が iOS-sfic です。apple-mobile-web-app-capableapple-mobile-web-app-status-bar-style は、アプリと同様の全画面モードでページ コンテンツをレンダリングし、ステータスバーを半透明にします。

 <meta name="apple-mobile-web-app-capable" content="yes">
 <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">

使用可能なすべてのメタ オプションの詳細については、Safari リファレンス ドキュメントをご覧ください。

ホーム画面のアイコン

iOS デバイスと Android デバイスでは、リンクに rel="apple-touch-icon"(iOS)と rel="apple-touch-icon-precomposed"(Android)も使用できます。ユーザーがサイトをブックマークすると、アプリのような華やかなアイコンがユーザーのホーム画面に表示されます。

 <link rel="apple-touch-icon"
      href="/static/images/identity/HTML5_Badge_64.png" />
 <link rel="apple-touch-icon-precomposed"
      href="/static/images/identity/HTML5_Badge_64.png" />

html5rocks がモバイル メタタグを使用する方法

まとめると、html5rocks の <head> セクションのスニペットは次のようになります。

 <head>
  ...
   <meta name="viewport"
        content="width=device-width, initial-scale=1.0, minimum-scale=1.0" />

   <link rel="apple-touch-icon"
        href="/static/images/identity/HTML5_Badge_64.png" />
   <link rel="apple-touch-icon-precomposed"
        href="/static/images/identity/HTML5_Badge_64.png" />
  ...
 </head>

縦長レイアウト

小さい画面では、横方向よりも縦方向にスクロールするほうがはるかに便利です。モバイルでは、コンテンツを 1 列の縦型レイアウトにすることをおすすめします。html5rocks では、CSS3 メディアクエリを使用してこのようなレイアウトを作成しました。マークアップを変更せずに、

チュートリアル インデックス。 チュートリアル HTML5 機能ページ。 著者のプロフィール ページ。
サイト全体が 1 列の縦型レイアウト。

モバイル向けの最適化

行った最適化のほとんどは、最初から行うべきものでした。ネットワーク リクエスト数の削減、JS/CSS 圧縮、gzip 圧縮(App Engine では無料)、DOM 操作の最小化などです。これらの手法は一般的なベスト プラクティスですが、サイトを急いで公開する際に見落とされがちです。

アドレスバーを自動で非表示にする

モバイル ブラウザには、デスクトップ ブラウザのような画面領域がありません。最悪なことに、プラットフォームが異なれば、ページの読み込みが完了しても、画面上部に古いアドレスバーが大きく表示される場合があります。

これを簡単に解決する方法の一つは、JavaScript を使用してページをスクロールすることです。1 ピクセルでも移動すれば、邪魔なアドレスバーを消すことができます。html5rocks のアドレスバーを強制的に非表示にするには、window オブジェクトに onload イベント ハンドラを接続し、ページを垂直方向に 1 ピクセルスクロールしました。

アドレスバー。
見苦しいアドレスバーが画面の表示領域を占めています。
  // Hides mobile browser's address bar when page is done loading.
  window.addEventListener('load', function(e) {
    setTimeout(function() { window.scrollTo(0, 1); }, 1);
  }, false);

また、このリスナーはパソコンでは必要ないため、is_mobile テンプレート変数でラップしています。

ネットワーク リクエストを減らし、帯域幅を節約する

HTTP リクエストの数を減らすとパフォーマンスが大幅に向上することは周知の事実です。モバイル デバイスでは、ブラウザが確立できる同時接続数がさらに制限されるため、モバイル サイトでは、こうした不要なリクエストを減らすことでさらに大きなメリットが得られます。さらに、スマートフォンでは帯域幅が制限されることが多いため、すべてのバイトを削ることが非常に重要です。ユーザーにお金を払っている可能性があります。

html5rocks でネットワーク リクエストを最小限に抑え、帯域幅を削減するために、次のようなアプローチを採用しました。

  • iframe を削除する - iframe は遅い!レイテンシの大部分は、チュートリアル ページのサードパーティ共有ウィジェット(Buzz、Google Friend Connect、Twitter、Facebook)によるものです。これらの API は <script> タグで追加され、ページの速度を低下させる iframe を作成します。モバイル向けのウィジェットが削除されました。

  • display:none - 一部のケースでは、マークアップがモバイル プロファイルに適合しない場合、マークアップが非表示になっていました。ホームページの上部にある 4 つの丸いボックスが良い例です。

ホームページのボックスボタン。
ホームページのボックスボタン。

モバイルサイトには表示されません。コンテナが display:none で非表示になっている場合でも、ブラウザはアイコンごとにリクエストを行うことに注意してください。そのため、単にこれらのボタンを非表示にするだけでは不十分でした。これは帯域幅を浪費するだけでなく、浪費した帯域幅の成果をユーザーが目にすることもありません。解決策として、Django テンプレートに「is_mobile」ブール値を作成し、HTML のセクションを条件付きで省略しました。ユーザーがスマート デバイスでサイトを表示している場合、ボタンが省略されています。

  • アプリケーション キャッシュ - オフライン サポートが提供されるだけでなく、起動も高速化されます。

  • CSS / JS 圧縮 - 主に CSS と JS の両方を処理するため、Closure Compiler ではなく YUI Compressor を使用しています。発生した問題の 1 つは、インライン メディアクエリ(スタイルシート内に表示されるメディアクエリ)が YUI Compressor 2.4.2 でエラーになるというものです(こちらの問題を参照)。YUI Compressor 2.4.4 以降を使用すると、問題は解決しました。

  • 可能な限り CSS 画像スプライトを使用します。

  • 画像圧縮に pngcrush を使用。

  • 小さな画像に dataURI を使用。Base64 エンコードでは、画像のサイズが 30%以上増加しますが、ネットワーク リクエストが節約されます。

  • Google カスタム検索を google.load() で動的に読み込むのではなく、1 つのスクリプトタグを使用して自動読み込みしました。後者は追加のリクエストを送信します。

<script src="//www.google.com/jsapi?autoload={"modules":[{"name":"search","version":"1"}]}"> </script>
  • コードの美化ツールと Modernizr は、使用されていなくてもすべてのページに含まれていました。Modernizr は優れたツールですが、読み込みごとに一連のテストが実行されます。これらのテストの中には、DOM にコストのかかる変更を加えてページの読み込みを遅くするものもあります。これらのライブラリは、実際に必要なページにのみ含めるようにしました。-2 リクエスト

パフォーマンスのその他の調整:

  • すべての JS をページの一番下に移動しました(可能な場合)。
  • インライン <style> タグを削除しました。
  • キャッシュに保存された DOM ルックアップと最小限の DOM 操作 - DOM にアクセスするたびに、ブラウザは再フローを実行します。リフローは、モバイル デバイスではさらにコストがかかります。
  • 無駄なクライアントサイド コードをサーバーにオフロードしました。具体的には、現在のページのナビゲーション スタイルを設定するチェックボックスです。 js var lis = document.querySelectorAll('header nav li'); var i = lis.length; while (i--) { var a = lis[i].querySelector('a'); var section = a.getAttribute("data-section"); if (new RegExp(section).test(document.location.href)) { a.className = 'current'; } }
  • 幅が固定された要素は、フルード width:100% または width:auto に置き換えられました。

アプリケーション キャッシュ

html5rocks のモバイル版では、アプリケーション キャッシュを使用して初期読み込みを高速化し、ユーザーがオフラインでコンテンツを読めるようにしています。

AppCache をサイトに実装する場合は、マニフェスト ファイルをキャッシュに保存しないことが非常に重要です(マニフェスト ファイル自体で明示的に行うか、キャッシュ コントロール ヘッダーの負荷が高い状態で暗黙的にキャッシュします)。ブラウザによってマニフェストがキャッシュされた場合、デバッグは悪夢です。iOS と Android では、このファイルのキャッシュは特に適切に行われますが、パソコンのブラウザのようにキャッシュをフラッシュする便利な方法がありません。

サイトのキャッシュ保存を防ぐため、まず、マニフェスト ファイルをキャッシュに保存しないように App Engine を設定しました。

- url: /(.*\.(appcache|manifest))
  static_files: \1
  mime_type: text/cache-manifest
  upload: (.*\.(appcache|manifest))
  expiration: "0s"

次に、JS API を使用して、新しいマニフェストのダウンロードが完了したことをユーザーに通知します。ページを更新するように求められます。

window.applicationCache.addEventListener('updateready', function(e) {
  if (window.applicationCache.status == window.applicationCache.UPDATEREADY) {
    window.applicationCache.swapCache();
    if (confirm('A new version of this site is available. Load it?')) {
      window.location.reload();
    }
  }
}, false);

ネットワーク トラフィックを節約するため、マニフェストをシンプルに保ってください。つまりサイトのすべてのページを 呼び出す必要はありません重要な画像、CSS、JavaScript ファイルのみをリストします。アプリキャッシュの更新のたびに、モバイル ブラウザに大量のアセットを強制的にダウンロードさせるのは避けるべきです。代わりに、ユーザーがアクセスしたときにブラウザが HTML ページを暗黙的にキャッシュに保存することを覚えておいてください(<html manifest="..."> 属性が含まれている場合)。