メディアクエリは便利ですが…
メディアクエリは、さまざまなサイズのデバイスでユーザーに優れたエクスペリエンスを提供するために、スタイルシートに小さな調整を加えたいウェブサイト デベロッパーにとって、まさに天の恵みです。メディアクエリを使用すると、画面サイズに応じてサイトの CSS をカスタマイズできます。この記事を読む前に、レスポンシブ デザインについて詳しく学び、mediaqueri.es でメディアクエリの使用例を確認してください。
以前の記事で Brad Frost が指摘しているように、モバイルウェブ向けに構築する際に考慮すべきことは、見た目の変更だけではありません。モバイル ウェブサイトの構築時にメディア クエリを使用してレイアウトをカスタマイズするだけの場合は、次のようになります。
- すべてのデバイスが同じ JavaScript、CSS、アセット(画像、動画)を取得するため、読み込み時間が長くなります。
- すべてのデバイスで同じ初期 DOM が取得されるため、デベロッパーは複雑すぎる CSS を記述しなければならない可能性があります。
- デバイスごとにカスタマイズされたカスタム インタラクションを指定する柔軟性が低い。
ウェブアプリにはメディアクエリ以上のものが必要
誤解しないでください。メディアクエリによるレスポンシブ デザインを嫌っているわけではありませんし、この手法が世界で活躍する場所があることは間違いありません。また、上記のいくつかの問題は、レスポンシブ画像や動的スクリプト読み込みなどのアプローチで解決できます。ただし、ある時点で、増分調整が多すぎることに気づき、異なるバージョンを提供した方がよいと判断するかもしれません。
作成する UI の複雑性が増し、シングルページ ウェブアプリに移行するにつれて、デバイスの種類ごとに UI をカスタマイズする作業が増えていきます。この記事では、最小限の労力でこれらのカスタマイズを行う方法について説明します。一般的なアプローチでは、サイト訪問者のデバイスを適切なデバイスクラスに分類し、そのデバイスに適切なバージョンを提供しながら、バージョン間のコードの再利用を最大化します。
どのデバイス クラスをターゲットにしていますか?
インターネットに接続されたデバイスは数多く存在し、そのほとんどにブラウザが搭載されています。複雑なのは、その多様性です。Mac ノートパソコン、Windows ワークステーション、iPhone、iPad、タッチ入力、スクロール ホイール、キーボード、音声入力、圧力感応デバイス、スマートウォッチ、トースター、冷蔵庫などを備えた Android スマートフォンなど、さまざまなデバイスがあります。これらのデバイスには、どこにでもあるものもあれば、非常に希少なものもあります。
優れたユーザー エクスペリエンスを実現するには、ユーザーが誰で、どのようなデバイスを使用しているかを知る必要があります。マウスとキーボードを使用するデスクトップ ユーザー向けにユーザー インターフェースを作成し、それをスマートフォン ユーザーに提供すると、インターフェースは別の画面サイズと別の入力方法向けに設計されているため、ユーザーは不満を感じます。
アプローチのスペクトラムには、次の 2 つの極端な方法があります。
すべてのデバイスで動作する 1 つのバージョンをビルドします。デバイスごとに設計上の考慮事項が異なるため、UX が損なわれる可能性があります。
サポートするデバイスごとにバージョンをビルドします。アプリケーションのバージョンを大量にビルドすることになるため、この処理はいつまでも終わりません。また、次の新しいスマートフォンがリリースされるたびに(ほぼ毎週)、別のバージョンを作成する必要があります。
ここには基本的なトレードオフがあります。デバイス カテゴリが多いほど、ユーザー エクスペリエンスは向上しますが、設計、実装、保守に要する作業は増えます。
パフォーマンス上の理由から、または異なるデバイスクラスに提供するバージョンが大きく異なる場合は、デバイスクラスごとに別々のバージョンを作成することをおすすめします。それ以外の場合は、レスポンシブ ウェブデザインが妥当なアプローチです。
考えられる解決策
そこで、デバイスをカテゴリに分類し、各カテゴリに最適なエクスペリエンスを設計するという妥協案を提案します。選択するカテゴリは、商品とターゲット ユーザーによって異なります。以下は、現在普及しているウェブ対応デバイスを網羅する分類の例です。
- 小さな画面 + タッチ(主にスマートフォン)
- 大画面 + タッチ(主にタブレット)
- 大画面 + キーボード/マウス(主にデスクトップ/ノートパソコン)
これは、考えられる分類の 1 つにすぎませんが、執筆時点では非常に理にかなった分類です。上記のリストには、タッチスクリーンのないモバイル デバイス(フィーチャー フォン、一部の専用電子書籍リーダーなど)は含まれていません。ただし、これらのほとんどにはキーボード ナビゲーションやスクリーン リーダー ソフトウェアがインストールされており、アクセシビリティを考慮してサイトを構築すれば問題なく動作します。
フォーム ファクタ固有のウェブアプリの例
さまざまなフォーム ファクタに対してまったく異なるバージョンを提供するウェブ プロパティの例は数多くあります。Google 検索や Facebook も同様です。この場合の考慮事項には、パフォーマンス(アセットの取得、ページのレンダリング)と、より一般的なユーザー エクスペリエンスの両方が含まれます。
ネイティブ アプリの世界では、多くのデベロッパーがデバイスクラスに合わせてエクスペリエンスを調整しています。たとえば、iPad 版の Flipboard は、iPhone 版の Flipboard とは UI が大きく異なります。タブレット バージョンは両手での使用と横方向のフリップ操作に最適化されており、スマートフォン バージョンは片手での操作と縦方向のフリップ操作を想定しています。Things(ToDo リスト)や Showyou(ソーシャル動画)など、他の多くの iOS アプリケーションも、スマートフォン版とタブレット版で大幅に異なる機能を提供しています。以下に例を示します。
アプローチ 1: サーバーサイド検出
サーバー側では、処理対象のデバイスに関する情報がはるかに限られています。おそらく最も有用な手がかりは、すべてのリクエストの User-Agent ヘッダーで提供されるユーザー エージェント文字列です。そのため、ここでも同じ UA スニッフィング アプローチが機能します。実際、DeviceAtlas プロジェクトと WURFL プロジェクトはすでにこれを行っています(デバイスに関する多くの追加情報も提供しています)。
残念ながら、これらの方法にはそれぞれ独自の課題があります。WURFL は非常に大きく、20 MB の XML を含んでいるため、リクエストごとにサーバーサイドのオーバーヘッドが大きくなる可能性があります。パフォーマンス上の理由から XML を分割するプロジェクトもあります。DeviceAtlas はオープンソースではなく、使用するには有料ライセンスが必要です。
Detect Mobile Browsers プロジェクトなど、よりシンプルで無料の代替手段もあります。もちろん、デバイス検出の網羅性が低下するという欠点があります。また、モバイル デバイスとモバイル以外のデバイスの区別しか行われず、タブレットのサポートはアドホックな調整セットを通じてのみ提供されます。
アプローチ 2: クライアントサイドの検出
機能検出を使用すると、ユーザーのブラウザとデバイスについて多くのことを知ることができます。デバイスにタッチ機能があるかどうか、画面が大きいか小さいかを判断する必要があります。
小さなタッチデバイスと大きなタッチデバイスを区別するために、どこかで線を引く必要があります。5 インチの Galaxy Note などのエッジケースはどうなりますか?次の図は、人気のある Android デバイスと iOS デバイスを重ねて表示したものです(対応する画面解像度も示されています)。アスタリスクは、デバイスが倍密度のもの、または倍密度のものになる可能性があることを示します。ピクセル密度は 2 倍になる可能性がありますが、CSS は同じサイズをレポートします。
CSS のピクセルに関する補足: モバイルウェブの CSS ピクセルは画面のピクセルと同じではありません。iOS の Retina デバイスでは、ピクセル密度を 2 倍にするという手法が導入されました(iPhone 3GS と 4、iPad 2 と 3 など)。Retina のモバイル Safari の UA は、ウェブの破損を避けるため、同じデバイス幅を報告します。他のデバイス(Android)で高解像度ディスプレイが採用されている場合、同じデバイス幅のトリックが使われています。
ただし、縦向きと横向きの両方のモードを考慮することが重要であるため、この決定は複雑になります。デバイスの向きを変えるたびにページを再読み込みしたり、追加のスクリプトを読み込んだりしたくありませんが、ページのレンダリング方法を変えたい場合があります。
次の図では、正方形は縦向きと横向きの輪郭を重ねて正方形にした結果として、各デバイスの最大寸法を表しています。
しきい値を 650px に設定すると、iPhone と Galaxy Nexus は smalltouch に分類され、iPad と Galaxy Tab は「タブレット」に分類されます。この場合、中性的な Galaxy Note は「電話」として分類され、電話レイアウトが適用されます。
妥当な戦略は次のようになります。
if (hasTouch) {
if (isSmall) {
device = PHONE;
} else {
device = TABLET;
}
} else {
device = DESKTOP;
}
機能検出アプローチの最小限のサンプルをご覧ください。
別の方法として、UA スニッフィングを使用してデバイスタイプを検出する方法があります。基本的には、一連のヒューリスティックを作成し、ユーザーの navigator.userAgent と照合します。擬似コードは次のようになります。
var ua = navigator.userAgent;
for (var re in RULES) {
if (ua.match(re)) {
device = RULES[re];
return;
}
}
UA 検出アプローチのサンプルをご覧ください。
クライアントサイドの読み込みに関する注意事項
サーバーで UA 検出を行う場合は、新しいリクエストを受け取ったときに配信する CSS、JavaScript、DOM を決定できます。ただし、クライアントサイドの検出を行う場合は、状況がより複雑になります。次のオプションがあります。
- このデバイスタイプのバージョンを含むデバイスタイプ固有の URL にリダイレクトします。
- デバイスタイプ固有のアセットを動的に読み込みます。
最初のアプローチは簡単で、window.location.href = '/tablet' などのリダイレクトが必要です。ただし、現在では、このデバイスタイプ情報がロケーションに追加されるため、History API を使用して URL をクリーンアップすることをおすすめします。残念ながら、このアプローチではリダイレクトが発生するため、特にモバイル デバイスでは処理が遅くなる可能性があります。
2 番目のアプローチは、実装がかなり複雑になります。CSS と JS を動的に読み込むメカニズムが必要になります。また、ブラウザによっては、<meta viewport> のカスタマイズなどの操作ができない場合があります。また、リダイレクトがないため、配信された元の HTML がそのまま使用されます。もちろん、JavaScript で操作することもできますが、アプリケーションによっては、処理が遅くなったり、エレガントでなくなったりする可能性があります。
クライアントまたはサーバーの決定
アプローチ間のトレードオフは次のとおりです。
Pro クライアント:
- UA ではなく画面サイズ/機能に基づいているため、将来を見据えた対応が可能。
- UA リストを常に更新する必要がない。
Pro サーバー:
- どのデバイスにどのバージョンを配信するかを完全に制御できます。
- パフォーマンスの向上: クライアントのリダイレクトや動的読み込みが不要になります。
個人的には、device.js とクライアントサイドの検出から始めることをおすすめします。アプリケーションの進化に伴い、クライアントサイドのリダイレクトがパフォーマンス上の大きな欠点であることが判明した場合は、device.js スクリプトを簡単に削除して、サーバーで UA 検出を実装できます。
device.js のご紹介
Device.js は、特別なサーバーサイド構成を必要とせずに、セマンティックなメディア クエリベースのデバイス検出を行うための出発点です。ユーザー エージェント文字列の解析に必要な時間と労力を節約できます。
このアイデアは、サイトのどのバージョンを提供したいかを示す検索エンジン フレンドリーなマークアップ(link rel=alternate)を <head> の先頭に指定するというものです。
<link rel="alternate" href="http://foo.com" id="desktop"
media="only screen and (touch-enabled: 0)">
次に、サーバーサイドで UA 検出を行い、バージョン リダイレクトを独自に処理するか、device.js スクリプトを使用して機能ベースのクライアントサイド リダイレクトを行うことができます。
詳しくは、device.js プロジェクト ページと、クライアントサイドのリダイレクトに device.js を使用するフェイク アプリケーションをご覧ください。
推奨: フォーム ファクタ固有のビューを含む MVC
ここまで読んで、デバイスタイプごとに 1 つずつ、完全に独立した 3 つのアプリを構築する必要があるとお考えかもしれません。いいえ。コード共有が鍵となります。
Backbone や Ember などの MVC 類似のフレームワークを使用していることを願っています。使用している場合は、関心の分離の原則、特に UI(ビューレイヤ)をロジック(モデルレイヤ)から分離する必要があることをご存じでしょう。MVC を初めて使用する場合は、MVC に関するリソースと JavaScript の MVC をご覧ください。
クロスデバイス ストーリーは、既存の MVC フレームワークにうまく適合します。ビューを簡単に別のファイルに移動して、デバイスタイプごとにカスタムビューを作成できます。これにより、ビューレイヤを除くすべてのデバイスに同じコードを提供できます。
プロジェクトの構造は次のようになります(もちろん、アプリケーションに応じて最も理にかなった構造を自由に選択できます)。
models/(共有モデル) item.js item-collection.js
controllers/(共有コントローラ) item-controller.js
versions/(デバイス固有のファイル) tablet/ desktop/ phone/(スマートフォン固有のコード) style.css index.html views/ item.js item-list.js
このような構造にすると、デバイスごとにカスタムの HTML、CSS、JavaScript を用意できるため、各バージョンで読み込むアセットを完全に制御できます。これは非常に強力で、アダプティブ画像などのトリックに頼ることなく、クロスデバイス ウェブ向けの最も効率的でパフォーマンスの高い開発方法につながります。
お気に入りのビルドツールを実行すると、すべての JavaScript と CSS が 1 つのファイルに連結され、圧縮されて読み込みが高速化されます。本番環境の HTML は次のようになります(スマートフォン向け、device.js を使用)。
<!doctype html>
<head>
<title>Mobile Web Rocks! (Phone Edition)</title>
<!-- Every version of your webapp should include a list of all
versions. -->
<link rel="alternate" href="http://foo.com" id="desktop"
media="only screen and (touch-enabled: 0)">
<link rel="alternate" href="http://m.foo.com" id="phone"
media="only screen and (max-device-width: 650px)">
<link rel="alternate" href="http://tablet.foo.com" id="tablet"
media="only screen and (min-device-width: 650px)">
<!-- Viewport is very important, since it affects results of media
query matching. -->
<meta name="viewport" content="width=device-width">
<!-- Include device.js in each version for redirection. -->
<script src="device.js"></script>
<link rel="style" href="phone.min.css">
</head>
<body>
<script src="phone.min.js"></script>
</body>
(touch-enabled: 0) メディアクエリは標準ではありません(Firefox でのみ moz ベンダー接頭辞の背後で実装されています)が、device.js によって正しく処理されます(Modernizr.touch のおかげです)。
バージョンのオーバーライド
デバイスの検出が誤って行われることもあります。また、ユーザーがスマートフォンでタブレット レイアウトを見たい場合(Galaxy Note を使用している場合など)もあるため、手動でオーバーライドしたい場合に、サイトのどのバージョンを使用するかをユーザーが選択できるようにすることが重要です。
一般的な方法は、モバイル版からパソコン版へのリンクを提供することです。この実装は簡単ですが、device.js は device GET パラメータでこの機能をサポートしています。
結論
まとめると、レスポンシブ デザインの世界にうまく収まらないクロスデバイスのシングルページ UI を構築する場合は、次の手順を行います。
- サポートするデバイスクラスのセットと、デバイスをクラスに分類する基準を選択します。
- 懸念事項を明確に分離し、ビューをコードベースの残りの部分から分割して、MVC アプリを構築します。
- device.js を使用して、クライアント サイドでデバイス クラスを検出します。
- 準備ができたら、スクリプトとスタイルシートをデバイス クラスごとに 1 つずつパッケージ化します。
- クライアントサイドのリダイレクトのパフォーマンスに問題がある場合は、device.js を使用せず、サーバーサイドの UA 検出に切り替えます。