今日の複雑なデバイス環境における特徴の一つは、非常に幅広い画面ピクセル密度が使用できることです。非常に高解像度のディスプレイを搭載しているデバイスもあれば、背後に配置されているデバイスもあります。アプリ デベロッパーはさまざまなピクセル密度をサポートする必要があり、これは非常に困難な場合があります。モバイルウェブでは、次のようないくつかの要因によって課題が複雑になります。
- フォーム ファクタが異なる多種多様なデバイス。
- ネットワーク帯域幅とバッテリー駆動時間の制約。
画像に関して言えば、ウェブアプリ デベロッパーはできる限り効率的に高品質の画像を提供することを目標としています。この記事では、現在および近い将来にこれを行うために役立つ手法について説明します。
できる限り画像は使用しない
ワームの缶を開く前に、ウェブには解像度と DPI に大きく依存しない優れたテクノロジーが多数存在することを覚えておいてください。具体的には、ウェブの(devicePixelRatio による)自動ピクセル スケーリング機能によって、テキスト、SVG、および CSS の多くが「そのまま機能」します。
とはいえ、常にラスター画像を使う必要はないわけではありません。たとえば、純粋な SVG や CSS では再現するのが非常に難しいアセットが与えられた場合や、写真を扱っている場合などです。画像を SVG に自動的に変換することもできますが、写真をベクトル化しても意味はありません。通常、拡大したバージョンは見栄えが良くないためです。
背景
ごく短いディスプレイ密度の歴史
初期のパソコンのディスプレイのピクセル密度は 72 または 96 dpi(1 インチあたりのドット数)でした。
ディスプレイのピクセル密度は徐々に改善されます。これは主に、ユーザーがスマートフォンを顔に近づけてピクセルを目立たせるというモバイルのユースケースに左右されます。2008 年には 150 dpi のスマートフォンがニューノーマルになりました。ディスプレイ密度の増加の傾向は今後も続き、今日の新しいスマートフォンは 300 dpi ディスプレイ(Apple の「Retina」ブランド)を搭載しています。
もちろん、究極の目標は、ピクセルが完全に見えないディスプレイです。スマートフォンのフォーム ファクタに関しては、現在の世代の Retina/HiDPI ディスプレイはその理想に近い可能性があります。ただし、Project Glass などの新しいクラスのハードウェアやウェアラブルでは、引き続きピクセル密度が増加する可能性があります。
実際には、低密度の画像は新しい画面でも古い画面と同じように表示されますが、高密度のユーザーが慣れ親しんでいる鮮明な画像と比べると、低密度の画像は不快でモザイク状になります。以下は、1 倍の画像が 2 倍のディスプレイでどのように表示されるかの大まかなシミュレーションです。対照的に、2x の画像はきれいに見えます。
ウェブ上の Google Pixel
ウェブが設計されたとき、ディスプレイの 99% は 96 dpi(またはふりをして)であり、この面でバリエーションのための規定はほとんど行われていませんでした。画面サイズと密度には大きなばらつきがあるため、さまざまな画面密度と寸法で画像がきれいに見えるようにするための標準的な方法が必要でした。
最近、HTML 仕様では、メーカーが CSS ピクセルのサイズを決定するために使用する参照ピクセルを定義することで、この問題に取り組みました。
メーカーは参照ピクセルを使用して、標準ピクセルまたは理想ピクセルに対するデバイスの物理ピクセルのサイズを決定できます。この比率は、デバイス ピクセル比と呼ばれます。
デバイスのピクセル比を計算する
スマートフォンの画面の物理ピクセルサイズが 180 ppi(1 インチあたり 180 ピクセル)であるとします。デバイスのピクセル比を計算するには、次の 3 つのステップが必要です。
デバイスがある実際の距離と、参照ピクセルまでの距離を比較します。
仕様上、28 インチでは 96 ピクセル/インチが理想的です。ただし、スマートフォンであるため、ユーザーはノートパソコンよりも顔の近くでデバイスを持ちます。その距離を 18 インチと見積もってみましょう
その距離の比率を標準密度(96 ppi)に掛けて、その距離に対する理想的なピクセル密度を求めます。
predictedPixelDensity = (28/18) × 96 = 150 ピクセル/インチ(約)
物理ピクセル密度と理想的なピクセル密度の比を求め、デバイスのピクセル比を求めます。
devicePixelRatio
= 180 ÷ 150 = 1.2
したがって、ブラウザが理想的な解像度または標準の解像度に応じて画面に合わせて画像のサイズを変更する方法を知る必要がある場合、ブラウザはデバイス ピクセル比 1.2 を参照します。つまり、理想的なピクセルごとに、このデバイスの物理ピクセル数は 1.2 になります。理想ピクセル(ウェブ仕様で定義されているピクセル)と物理ピクセル(デバイス画面上のドット数)の差を求める式は次のとおりです。
physicalPixels = window.devicePixelRatio * idealPixels
デバイス ベンダーはこれまで、devicePixelRatios
(DPR)を四捨五入する傾向がありました。Apple の iPhone と iPad の DPR は 1 で、Retina の同等のレポートでは 2 となっています。CSS 仕様では、
ピクセル単位とは、参照ピクセルを最適に近似するデバイス ピクセルの数を指します。
丸め率を改善できる理由の 1 つは、サブピクセル アーティファクトが少なくなる可能性があるためです。
しかし、デバイスを取り巻く状況はもっとさまざまであり、Android スマートフォンの DPR は多くの場合 1.5 です。Nexus 7 タブレットの DPR は約 1.33 で、上記と同様の計算で算出されました。今後、DPR が変動するデバイスが増えると予想されます。このため、クライアントが整数の DPR を持つとは想定しないでください。
HiDPI 画像手法の概要
高品質の画像をできるだけ早く表示する問題を解決するための手法は多数あり、大きく分けて次の 2 つのカテゴリに分類されます。
- 単一の画像の最適化
- 複数の画像の選択を最適化します。
単一画像を使用する: 画像を 1 つ使用しますが、それに工夫をこらします。この方法には、低 DPI の古いデバイスでも HiDPI イメージがダウンロードされるため、必然的にパフォーマンスが犠牲になるという欠点があります。画像が 1 つの場合のアプローチをいくつか次に示します。
- 高圧縮の HiDPI 画像
- 非常に優れた画像フォーマット
- プログレッシブ画像フォーマット
複数の画像を使用する: 複数の画像を使用しますが、読み込む画像の選択を慎重に行います。このようなアプローチでは、デベロッパーが同じアセットの複数のバージョンを作成して決定戦略を見つけるという、本質的なオーバーヘッドが発生します。指定できる広告タイプは次のとおりです。
- JavaScript
- サーバーサイドの配信
- CSS メディアクエリ
- 組み込みのブラウザ機能(
image-set()
、<img srcset>
)
高圧縮の HiDPI 画像
画像はすでに、平均的なウェブサイトのダウンロードに費やされる帯域幅の 60% を占めています。すべてのクライアントに HiDPI 画像を提供することで、この数を増やします。どれほど大きく成長するか。
いくつかのテストを実行し、JPEG 画質 90、50、20 の 1 倍と 2 倍の画像フラグメントを生成しました。環境変数を生成するために使用したシェル スクリプト(ImageMagick を使用)を示します。
この小規模で非科学的なサンプリングでは、大きな画像を圧縮した場合の品質とサイズとのトレードオフが優れているように見えます。かなり圧縮された 2 倍の画像は、圧縮されていない 1 倍の画像よりも見栄えが良いと感じます。
もちろん、低品質で高度に圧縮された 2 倍の画像を 2 倍のデバイスに配信するのは、高品質の画像を提供するよりも悪く、上記のアプローチでは画質が低下します。画質(90 枚の画像)と画質(20 枚)を比較すると、鮮明さが低下し、粗さが増しています。こうしたアーティファクトは、高品質の画像が鍵となる場合(写真ビューア アプリなど)や、アプリ デベロッパーが妥協を許さない場合、許容されない可能性があります。
上記の比較はすべて圧縮 JPEG で行われました。広く実装されている画像形式(JPEG、PNG、GIF)の間には多くのトレードオフがあり、次のようなことが実現されます。
非常に優れた画像フォーマット
WebP は、高い画像忠実度を維持しながら非常に優れた圧縮を行う、非常に魅力的な画像形式です。もちろん、まだどこでも実装されているわけではありません。
1 つの方法は、JavaScript を使用して WebP がサポートされているかどうかを確認することです。data-uri を使用して 1px の画像を読み込み、Loaded イベントまたは error イベントが発生するのを待ってから、サイズが正しいことを確認します。Modernizr には、このような機能検出スクリプトが付属しており、Modernizr.webp
を介して使用できます。
ただし、より適切な方法としては、CSS で image() 関数を直接使用する方法があります。そのため、WebP 画像と JPEG のフォールバックがある場合は、次のように記述できます。
#pic {
background: image("foo.webp", "foo.jpg");
}
この方法にはいくつか問題があります。まず、image()
は広く実装されていません。次に、WebP 圧縮は JPEG を吹き飛ばしますが、それでも比較的改善されており、こちらの WebP ギャラリーによると約 30% 小さくなっています。したがって、WebP だけでは高 DPI の問題に対処するには不十分です。
プログレッシブ画像形式
JPEG 2000、プログレッシブ JPEG、プログレッシブ PNG、GIF などのプログレッシブ画像形式には、画像が完全に読み込まれる前に表示されるという利点があります。サイズのオーバーヘッドが発生する可能性はありますが、これについては相反する証拠もあります。Jeff Atwood 氏は、プログレッシブ モードでは「PNG 画像のサイズは約 20%、JPEG および GIF 画像のサイズは約 10% 増える」と主張しています。しかし、Stoyan Stefanov は、大きなファイルの場合はプログレッシブ モードの方が(ほとんどの場合)効率的であると主張しています。
一見すると、プログレッシブ画像は、最高品質の画像をできるだけ早く提供するという点で非常に有望です。これは、データを追加しても画質は向上しない(つまり、忠実度の改善がすべてサブピクセルになる)ことをブラウザが認識した時点で、画像のダウンロードとデコードを停止できるという考え方です。
接続は簡単に終了できますが、再起動にはコストがかかることがよくあります。多数の画像があるサイトの場合、最も効率的なアプローチは、1 つの HTTP 接続を維持し、可能な限り長く再利用することです。1 つの画像が十分にダウンロードされたために接続が早期に終了した場合、ブラウザは新しい接続を作成する必要があります。これは、低レイテンシの環境では非常に遅い可能性があります。
回避策の 1 つは、HTTP Range リクエストを使用することです。これにより、ブラウザは取得するバイトの範囲を指定できます。スマート ブラウザでは、HEAD リクエストを実行してヘッダーを取得し、処理し、実際に画像の量を判断して取得します。残念ながら、ウェブサーバーでは HTTP Range が十分にサポートされていないため、このアプローチは実用的ではありません。
最後に、このアプローチの明らかな制限は、読み込む画像を選択できず、同じ画像でも忠実度が異なるだけです。そのため、「アート ディレクション」のユースケースには対応しません。
JavaScript を使用して読み込む画像を決定する
読み込む画像を決定する最も明白なアプローチは、クライアントで JavaScript を使用することです。この方法により、ユーザー エージェントに関するすべての情報を確認し、適切な処理を行うことができます。デバイスのピクセル比は window.devicePixelRatio
で判断でき、画面の幅と高さを取得できます。また、navigator.connection でネットワーク接続をスニッフィングしたり、foresight.js ライブラリのように疑似リクエストを発行したりすることもできます。これらの情報をすべて収集したら、読み込む画像を決定できます。
このような処理を行う JavaScript ライブラリは約 100 万個ありますが、残念ながらいずれも特に優れたものはありません。
この方法の大きな欠点の一つは、JavaScript を使用すると、先読みパーサーが完了するまで画像の読み込みが遅れることです。つまり、pageload
イベントが発生するまで、画像のダウンロードも開始されません。詳しくは、Jason Grigsby の記事をご覧ください。
サーバーに読み込む画像を決定する
提供する画像ごとにカスタム リクエスト ハンドラを作成することで、サーバーサイドに決定を任せることができます。このようなハンドラは、User-Agent(サーバーに中継される唯一の情報)に基づいて Retina のサポートを確認します。次に、サーバー側のロジックで HiDPI アセットを配信するかどうかに基づいて、適切なアセット(既知の規則に従った名前が付けられている)を読み込みます。
残念ながら User-Agent は、デバイスで高画質と低画質のどちらの画像を受け取るかを判断するのに十分な情報を提供してくれるとは限りません。言うまでもなく、User-Agent に関連するものはハッキングであるため、可能な限り回避する必要があります。
CSS メディアクエリを使用する
CSS メディアクエリは宣言型であるため、ユーザーの意図を表明して、ブラウザがユーザーの代わりに適切な処理を行うことができます。メディアクエリの一般的な使用方法(デバイスサイズを一致させる方法)に加えて、devicePixelRatio
を一致させることもできます。関連付けられたメディアクエリはデバイス ピクセル比であり、ご想像のとおり、最小バリエーションと最大バリエーションが関連付けられています。高 DPI の画像を読み込む必要があり、デバイス ピクセル比がしきい値を超える場合は、次のようにします。
#my-image { background: (low.png); }
@media only screen and (min-device-pixel-ratio: 1.5) {
#my-image { background: (high.png); }
}
すべてのベンダー プレフィックスが混在していると、状況は少し複雑になります。これは、特に「min」と「max」のプレフィックスの配置の違いが異常であるためです。
@media only screen and (min--moz-device-pixel-ratio: 1.5),
(-o-min-device-pixel-ratio: 3/2),
(-webkit-min-device-pixel-ratio: 1.5),
(min-device-pixel-ratio: 1.5) {
#my-image {
background:url(high.png);
}
}
このアプローチにより、JS ソリューションでは失われていた先読み解析のメリットを活用できます。また、サーバーサイドのアプローチでは失われていた、レスポンシブなブレークポイントを柔軟に選択できるようになります(たとえば、低 DPI、中 DPI、高 DPI の画像を用意できます)。
残念ながら、やはりやや扱いづらく、CSS がおかしくなります(または前処理が必要になります)。また、この方法は CSS プロパティに限られるため、<img src>
を設定する方法はありません。画像はすべて背景のある要素にする必要があります。最後に、デバイス ピクセル比に厳密に依存すると、EDGE 接続中に高 DPI スマートフォンが大量の 2 倍の画像アセットをダウンロードすることになりかねません。これは最適なユーザー エクスペリエンスではありません。
新しいブラウザ機能を使用する
最近、高 DPI の画像の問題に対するウェブ プラットフォームのサポートに関して多くの議論がなされています。最近 Apple はこの分野に参入し、image-set() CSS 関数を WebKit に導入しました。そのため、Safari と Chrome の両方でこの機能がサポートされています。これは CSS 関数であるため、image-set()
は <img>
タグの問題には対応しません。「@srcset」と入力します。これはこの問題に対処していますが、(このドキュメントの執筆時点で)リファレンス実装はまだありません。次のセクションでは、image-set
と srcset
について詳しく説明します。
高 DPI をサポートするブラウザ機能
どのアプローチを取るかは、最終的には特定の要件によって異なります。とはいえ、前述のどの方法にも欠点があります。将来的には、image-set
と srcset が広くサポートされるようになると、この問題に対する適切な解決策になるでしょう。とりあえず、理想的な未来にできるだけ近づけるためのベスト プラクティスについて説明しましょう。
まず、この 2 つは何が違うのでしょうか。image-set()
は、バックグラウンド CSS プロパティの値として使用するのに適した CSS 関数です。srcset は、同様の構文を持つ <img>
要素に固有の属性です。どちらのタグでも画像の宣言を指定できますが、srcset 属性を使用すると、ビューポートのサイズに基づいて読み込む画像も構成できます。
画像セットのベスト プラクティス
CSS 関数 image-set()
は -webkit-image-set()
という接頭辞で使用できます。構文は非常にシンプルで、1 つ以上のカンマ区切りの画像宣言を使用できます。これは、URL 文字列または url()
関数の後に関連する解像度を記述したものです。次に例を示します。
background-image: -webkit-image-set(
url(icon1x.jpg) 1x,
url(icon2x.jpg) 2x
);
これにより、2 つの画像から選択できます。1 つは 1 倍のディスプレイ用に最適化され、もう 1 つは 2 倍のディスプレイ用に最適化されています。 その後、ブラウザが十分にスマートである場合(現時点では私が知る限りは実装されていません)、ブラウザはさまざまな要因に基づいて、読み込むものを選択します。これにはネットワーク速度も含まれます。
ブラウザは正しい画像を読み込むだけでなく、それに応じて画像を拡大縮小します。つまり、ブラウザでは 2 つの画像のサイズが 1 倍の画像の 2 倍であると想定し、ページ上で同じサイズの画像が表示されるように 2 倍の画像を 2 倍に縮小します。
1x、1.5x、Nx を指定する代わりに、デバイスの特定のピクセル密度を dpi で指定することもできます。
この方法は問題なく機能しますが、image-set
プロパティをサポートしていないブラウザでは画像がまったく表示されません。これは明らかに問題であるため、フォールバック(または一連のフォールバック)を使用して問題に対処する必要があります。
background-image: url(icon1x.jpg);
background-image: -webkit-image-set(
url(icon1x.jpg) 1x,
url(icon2x.jpg) 2x
);
/* This will be useful if image-set gets into the platform, unprefixed.
Also include other prefixed versions of this */
background-image: image-set(
url(icon1x.jpg) 1x,
url(icon2x.jpg) 2x
);
上記のコードは、画像セットをサポートしているブラウザでは適切なアセットを読み込み、サポートしていない場合は 1x のアセットにフォールバックします。ただし、image-set()
ブラウザ サポートは低いものの、ほとんどのユーザー エージェントは 1x のアセットを受け取ることに注意してください。
このデモでは、image-set()
を使用して正しい画像を読み込みます。この CSS 関数がサポートされていない場合は 1x アセットにフォールバックします。
この時点で、なぜ image-set()
をポリフィル(つまり JavaScript shim のビルド)で終わらせるだけでは済まないのかと疑問に思われるかもしれません。結局のところ、CSS 関数に効率的なポリフィルを実装することは非常に困難です。(詳しい理由については、www スタイルのディスカッションをご覧ください)。
画像ソースセット
srcset の例を次に示します。
<img alt="my awesome image"
src="banner.jpeg"
srcset="banner-HD.jpeg 2x, banner-phone.jpeg 640w, banner-phone-HD.jpeg 640w 2x">
ご覧のとおり、srcset 要素は image-set
が提供する x 宣言に加えて、ビューポートのサイズに対応する w と h の値も受け取り、最も関連性の高いバージョンを提供しようとします。上記のコードでは、ビューポートの幅が 640 ピクセル未満のデバイスには Banner-phone.jpeg、画面が小画面の高 DPI デバイスには Banner-phone-HD.jpeg、画面が 640 ピクセルを超える高 DPI デバイスには banner-HD.jpeg、その他のすべてのデバイスには banner.jpeg が配信されます。
イメージ要素にイメージセットを使用する
ほとんどのブラウザでは img 要素の srcset 属性は実装されていないため、img 要素を <div>
に置き換えて、image-set アプローチを使用したくなるかもしれません。これは動作しますが、注意点があります。ここでの欠点は、<img>
タグに長期的なセマンティック値が含まれることです。実際には、これは主にウェブクローラーとユーザー補助の理由において重要です。
最終的に -webkit-image-set
を使用する場合は、バックグラウンド CSS プロパティを使用したくなるかもしれません。この方法の欠点は、画像サイズを指定する必要があることです。1x 以外の画像を使用している場合は、この値が不明です。その代わりに、次のようにコンテンツ CSS プロパティを使用することもできます。
<div id="my-content-image"
style="content: -webkit-image-set(
url(icon1x.jpg) 1x,
url(icon2x.jpg) 2x);">
</div>
これにより、devicePixelRatio に基づいて画像が自動的に拡大縮小されます。上記の手法の実際の動作についてはこちらの例をご覧ください。image-set
をサポートしていないブラウザの場合は、url()
へのフォールバックが追加されています。
srcset のポリフィル
srcset
の便利な機能の一つは、自然なフォールバックが付属していることです。srcset 属性が実装されていない場合、すべてのブラウザで src 属性が処理されます。また、これは単なる HTML 属性であるため、JavaScript でポリフィルを作成することもできます。
このポリフィルには、可能な限り仕様に近いことを確認するための単体テストが付属しています。さらに、srcset がネイティブに実装されている場合、ポリフィルがコードを実行しないようにするためのチェックも適用されます。
実際のポリフィルのデモをご覧ください。
まとめ
高 DPI 画像の問題を解決できる魔法の薬はありません。
最も簡単な解決策は、画像を完全に使用せず、SVG と CSS を使用することです。ただし、特にサイトに高品質の画像がある場合、これは必ずしも現実的であるとは限りません。
JS、CSS、サーバーサイドのアプローチには、どれも強みと弱みがあります。最も有望なアプローチは、新しいブラウザ機能を活用することです。image-set
と srcset
に対するブラウザ サポートはまだ不完全ですが、現時点で使用できる妥当なフォールバックがあります。
まとめると、私の推奨事項は次のとおりです。
- 背景画像には image-set を使用し、それをサポートしていないブラウザの場合は適切なフォールバックを指定します。
- コンテンツ画像の場合は、srcset ポリフィルを使用するか、image-set を使用するためのフォールバックを使用します(上記参照)。
- 画質を犠牲にしたい場合は、大幅に圧縮した 2 倍の画像の使用を検討してください。