はじめに
モバイルウェブ向けの開発は、最近注目されているトピックです。今年は、初めてスマートフォンの販売台数が PC を上回った年です。モバイル デバイスを使用してウェブを閲覧するユーザーが増えているため、デベロッパーにとって、モバイル ブラウザ向けにサイトを最適化することが重要になっています。
多くのデベロッパーにとって、「モバイル」の戦場はまだ未知の領域です。多くの企業には、モバイル ユーザーを完全に無視した既存のレガシー サイトがあります。代わりに、このサイトは主にパソコンでのブラウジング用に設計されており、モバイル ブラウザでは表示が不十分です。このサイト(html5rocks.com)も例外ではありません。リリース当初、サイトのモバイル版にはほとんど手を入れませんでした。
モバイル対応の html5rocks.com を作成する
演習として、html5rocks(既存の HTML5 サイト)をモバイル対応のバージョンに拡張してみることにしました。主に、スマートフォンをターゲットにするために必要な最小限の作業量について懸念していました。この演習の目的は、まったく新しいモバイルサイトを作成して 2 つのコードベースを維持することではありません。時間がかかり、時間の無駄になるため、サイトの構造(マークアップ)はすでに定義されています。見た目と感触(CSS)を検証しました。コア機能(JS)は存在していました。多くのサイトが同じ状況にあります。
この記事では、Android デバイスと iOS デバイス向けに最適化された html5rocks のモバイル版を作成する方法について説明します。これらの OS のいずれかをサポートするデバイスで html5rocks.com を読み込むだけで、違いを確認できます。m.html5rocks.com などのリダイレクトはありません。html5rocks はそのまま使用できますが、モバイル デバイスで美しく機能するメリットもあります。
CSS メディアクエリ
HTML4 と CSS2 では、メディア依存のスタイルシートがサポートされています。次に例を示します。
<link rel="stylesheet" media="print" href="printer.css">
は、印刷デバイスをターゲットにし、印刷時にページ コンテンツに特定のスタイルを適用します。CSS3 では、メディアタイプの概念をさらに一歩進め、メディアクエリによって機能を強化しています。メディアクエリを使用すると、スタイルシートをより正確にラベル付けできるため、メディアタイプの有用性が向上します。これにより、コンテンツ自体を変更することなく、出力デバイスの特定の範囲に合わせてコンテンツの表示をカスタマイズできます。変更が必要な既存のレイアウトに最適です。
外部スタイルシートの media
属性でメディアクエリを使用して、画面幅、デバイス幅、向きなどをターゲットに設定できます。一覧については、W3C メディアクエリ仕様をご覧ください。
画面サイズのターゲティング
次の例では、phone.css
は、ブラウザが「ハンドヘルド」と見なすデバイス、または画面の幅が 320 px 以下のデバイスに適用されます。
<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 px)と iPad(~768 px)に対応しています。効果: モバイルでの表示を改善するため、base.css
のスタイルを(必要に応じてのみ)段階的にオーバーライドしています。
mobile.css
が適用するスタイル変更の例:
- サイト全体の余白やパディングを削減します。画面が小さいということは、スペースが限られているということです。
:hover
状態を削除します。タッチデバイスでは表示されません。- レイアウトを単一列に調整します。詳しくは後で説明します。
- サイトのメイン コンテナ div の周囲の
box-shadow
を削除します。大きなボックスシャドウはページのパフォーマンスを低下させます。 - CSS フレックスボックス モデル
box-ordinal-group
を使用して、ホームページの各セクションの順序を変更しました。ホームページでは [主要な HTML5 機能グループ別に学習] が [チュートリアル] セクションの前に表示されますが、モバイル版ではその後に表示されます。この順序はモバイル向けに適しており、マークアップの変更も必要ありませんでした。CSS フレックス ボックス最高! opacity
の変更を削除します。アルファ値を変更すると、モバイルのパフォーマンスが低下します。
モバイル メタタグ
モバイル WebKit は、特定のデバイスでユーザーのブラウジング エクスペリエンスを向上させるいくつかの機能をサポートしています。
ビューポートの設定
最初のメタ設定(最もよく使用する設定)は、ビューポート プロパティです。ビューポートを設定すると、コンテンツをデバイスの画面にどのように収めるかをブラウザに指示し、サイトがモバイル向けに最適化されていることをブラウザに通知します。次に例を示します。
<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 では、デベロッパーがサイトが開発された画面解像度を指定できるようにすることで、ビューポート メタタグを拡張しています。
<meta name="viewport" content="target-densitydpi=device-dpi">
target-densitydpi
の想定される値は、device-dpi
、high-dpi
、medium-dpi
、low-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-capable
と apple-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 メディアクエリを使用してこのようなレイアウトを作成しました。マークアップを変更せずに、
モバイル向けの最適化
行った最適化のほとんどは、最初から行うべきものでした。ネットワーク リクエスト数の削減、JS/CSS 圧縮、gzipping(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 リクエストの数を減らすとパフォーマンスが大幅に向上することは周知の事実です。モバイル デバイスでは、ブラウザが確立できる同時接続数がさらに制限されるため、モバイル サイトでは、こうした不要なリクエストを減らすことでさらに大きなメリットが得られます。また、スマートフォンでは帯域幅が制限されることが多いので、1 バイトでも削減することが重要です。ユーザーに費用が発生する可能性があります。
html5rocks でネットワーク リクエストを最小限に抑え、帯域幅を削減するために、次のようなアプローチを採用しました。
iframe を削除する - iframe は遅いです。レイテンシの大部分は、チュートリアル ページのサードパーティの共有ウィジェット(Buzz、Google Friend Connect、Twitter、Facebook)によるものでした。これらの API は
<script>
タグで組み込まれ、ページの速度を低下させる iframe を作成します。モバイル向けのウィジェットが削除されました。display:none
- 一部のケースでは、マークアップがモバイル プロファイルに適合しない場合、マークアップが非表示になっていました。たとえば、ホームページの上部にある 4 つの丸いボックスは、
モバイルサイトには表示されません。コンテナが display:none
で非表示になっているにもかかわらず、ブラウザは各アイコンのリクエストを送信します。そのため、これらのボタンを単に非表示にするだけでは不十分でした。これは帯域幅を浪費するだけでなく、浪費した帯域幅の成果をユーザーが目にすることもありません。解決策として、Django テンプレートに「is_mobile」ブール値を作成し、HTML のセクションを条件付きで省略しました。ユーザーがスマート デバイスでサイトを表示している場合、ボタンは表示されません。
アプリケーション キャッシュ - オフライン サポートが提供されるだけでなく、起動も高速化されます。
CSS/JS 圧縮 - YUI 圧縮ツールは、CSS と JS の両方を処理できるため、Closure コンパイラではなく YUI 圧縮ツールを使用しています。発生した問題の 1 つは、インライン メディアクエリ(スタイルシート内に表示されるメディアクエリ)が YUI Compressor 2.4.2 でエラーになるというものです(こちらの問題を参照)。YUI Compressor 2.4.4 以降を使用すると、問題は修正されました。
可能な限り CSS 画像スプライトを使用します。
画像圧縮に pngcrush を使用。
小さな画像に dataURI を使用。Base64 エンコードでは画像のサイズが約 30%以上増加しますが、ネットワーク リクエストは削減されます。
google.load()
で動的に読み込むのではなく、単一のスクリプトタグを使用して Google カスタム検索を自動読み込み。後者は追加のリクエストを送信します。
<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"
2 つ目は、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="...">
属性が含まれている場合)。