今日の複雑なデバイス環境における特徴の一つは、非常に幅広い画面ピクセル密度を利用できることです。デバイスによっては非常に高解像度のディスプレイを搭載しているものもあれば、そうでないものもあります。アプリ デベロッパーは、さまざまなピクセル密度をサポートする必要がありますが、これは非常に困難です。モバイルウェブでは、次のような要因によって課題が複雑になります。
- さまざまなフォーム ファクタを持つ多種多様なデバイス。
- ネットワーク帯域幅とバッテリー駆動時間が制限される。
画像に関して、ウェブアプリ デベロッパーの目標は、最高品質の画像を可能な限り効率的に配信することです。この記事では、現在と近い将来に役立つテクニックについて説明します。
できるだけ画像の使用は避ける
ワームに関する話では、ウェブには解像度や DPI にほぼ依存しない強力なテクノロジーが多数存在することを覚えておいてください。具体的には、テキスト、SVG、ほとんどの CSS は、ウェブの自動ピクセル スケーリング機能(devicePixelRatio 経由)により「問題なく動作」します。
ただし、ラスター画像を避けられないこともあります。たとえば、純粋な SVG/CSS では複製が困難なアセットや、写真を扱う場合などです。画像を SVG に変換することはできますが、写真のベクター化は意味がありません。通常、拡大したバージョンは見栄えがよくありません。
背景
これまであまり使用されていない表示間隔です。
初期のコンピュータ ディスプレイのピクセル密度は 72 dpi または 96 dpi(1 インチあたりのドット数)でした。
ディスプレイの画素密度は、主にモバイル ユースケースを重視して徐々に向上してきました。モバイル ユースケースでは、ユーザーは通常スマートフォンを顔の近くに持ってきて、ピクセルをよりはっきりと見えるようにします。2008 年までに、150 dpi のスマートフォンが新しい標準になりました。ディスプレイの密度を高める傾向は続き、今日の新しいスマートフォンには 300 dpi のディスプレイ(Apple では「Retina」というブランド名)が搭載されています。
もちろん、ピクセルが完全に目立たないディスプレイが理想です。スマートフォンのフォーム ファクタの場合、現在の Retina / HiDPI ディスプレイは理想に近いと言えます。ただし、Project Glass などの新しいクラスのハードウェアやウェアラブルでは、引き続き画素密度が向上していくと考えられます。
実際には、低密度の画像は新しい画面でも古い画面と同じように表示されますが、高密度のユーザーがよく目にする鮮明な画像と比べると、低密度の画像は目障りでモザイク状になります。次の図は、2x ディスプレイに 1x 画像が表示された場合の概略的なシミュレーションです。一方、2 倍の画像は非常に良好です。
ウェブ上のピクセル
ウェブがデザインされた当時、ディスプレイの 99% は 96dpi(またはこのふりをするふりをする)であり、この点についてバリエーションを設ける用意はほとんどありませんでした。画面サイズと密度が大きく異なるため、さまざまな画面密度と寸法で画像を美しく表示するための標準的な方法が必要でした。
HTML 仕様では最近、メーカーが CSS ピクセルのサイズを決定するために使用する参照ピクセルを定義することで、この問題に対処しました。
メーカーは、参照ピクセルを使用して、標準ピクセルまたは理想ピクセルに対するデバイスの物理ピクセルのサイズを決定できます。この比率はデバイスのピクセル比と呼ばれます。
デバイスのピクセル比を計算する
スマートフォンの画面の物理的なピクセルサイズが 180 ppi であるとします。デバイスのピクセル比の計算は、次の 3 つのステップで行うことができます。
デバイスを保持する実際の距離と、参照ピクセルの距離を比較します。
仕様では、28 インチで 1 インチあたり 96 ピクセルが理想的とされています。ただし、スマートフォンはノートパソコンよりも顔に近づけて持つため、この距離を 18 インチと見積もります。
距離比を標準密度(96 ppi)に掛けて、指定した距離に最適なピクセル密度を取得します。
IdeaPixelDensity = (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 仕様では、
ピクセル単位は、参照ピクセルに最も近いデバイス ピクセルの整数値を指します。
ラウンド比を改善すると、サブピクセル アーティファクトが少なくなる可能性があります。
ただし、実際のデバイス環境ははるかに多様であり、Android スマートフォンの DPR は 1.5 であることが多いです。Nexus 7 タブレットの DPR は約 1.33 です。これは上記と同様の計算で得られた値です。今後、DPR が変動するデバイスが増えると予想されます。このため、クライアントに整数の DPR があると想定しないでください。
HiDPI 画像手法の概要
最高品質の画像をできるだけ早く表示する問題を解決するための手法は数多くありますが、大きく 2 つのカテゴリに分けられます。
- 単一画像の最適化
- 複数の画像の選択を最適化しています。
1 つの画像を使用するアプローチ: 1 つの画像を使用しますが、それを使ってなんらかの工夫をします。これらのアプローチには、低 DPI の古いデバイスでも HiDPI 画像をダウンロードするため、パフォーマンスが犠牲になるという欠点があります。単一画像の場合のアプローチは次のとおりです。
- 圧縮率の高い HiDPI 画像
- 非常に優れた画像形式
- プログレッシブ イメージ形式
複数の画像を使用するアプローチ: 複数の画像を使用しますが、読み込む対象を慎重に選びます。これらのアプローチには、同じアセットの複数のバージョンを作成し、意思決定戦略を策定するという、デベロッパーに固有のオーバーヘッドがあります。指定できる広告タイプは次のとおりです。
- JavaScript
- サーバーサイド配信
- CSS メディアクエリ
- ブラウザの組み込み機能(
image-set()
、<img srcset>
)
圧縮率の高い HiDPI 画像
画像は、平均的なウェブサイトのダウンロードに費やされる帯域幅の 60% を占めています。HiDPI 画像をすべてのクライアントに提供することで、この数を増やします。どれくらい大きくなりますか?
いくつかのテストを行い、JPEG 画質が 90、50、20 の 1x と 2x の画像フラグメントを生成するテストを実施しました。以下は、これらの画像を生成するために使用したシェル スクリプト(ImageMagick を使用)です。
この小さな非科学的なサンプリングから、大きな画像を圧縮すると、品質とサイズのバランスが取れているようです。私から見ると、圧縮率の高い 2x 画像は、非圧縮の 1x 画像よりも見栄えがよいように見えます。
もちろん、低画質で圧縮率の高い 2x 画像を 2x デバイスに配信することは、高画質の画像を配信するよりも劣ります。上記のアプローチでは、画質のペナルティが発生します。画質: 90 枚の画像と画質: 20 枚の画像を比較すると、鮮明さが低下し、粗さが目立つようになります。これらのアーティファクトは、高品質の画像が重要な場合(写真ビューア アプリケーションなど)や、妥協を許さないアプリ デベロッパーには許容されない場合があります。
上の比較は、すべて圧縮 JPEG で行われています。広く実装されている画像形式(JPEG、PNG、GIF)には多くのトレードオフがあることに注意してください。
非常に優れた画像形式
WebP は、高い画像忠実度を維持しながら非常に優れた圧縮を実現する、非常に魅力的な画像形式です。もちろん、まだどこにも実装されているわけではありません。
WebP のサポートを確認する方法の一つは、JavaScript を使用する方法です。data-uri を使用して 1 ピクセルの画像を読み込み、読み込みイベントまたはエラーイベントが発生するまで待ってから、サイズが正しいことを確認します。Modernizr には、このような機能検出スクリプトが付属しており、Modernizr.webp
から利用できます。
ただし、image() 関数を使用して CSS で直接行う方が適切です。そのため、WebP 画像と JPEG の代替がある場合は、次のように記述できます。
#pic {
background: image("foo.webp", "foo.jpg");
}
このアプローチにはいくつかの問題があります。まず、image()
は広く実装されていません。2 つ目は、WebP 圧縮は JPEG を圧倒的に上回るものの、この WebP ギャラリーに基づくと、サイズは約 30% 小さくなる程度で、相対的に改善は限定的です。したがって、WebP だけでは高 DPI の問題に対処するには不十分です。
プログレッシブ イメージ形式
JPEG 2000、プログレッシブ JPEG、プログレッシブ PNG、GIF などのプログレッシブ画像形式には、画像が完全に読み込まれる前に画像が表示されるというメリットがあります(議論の余地があります)。サイズのオーバーヘッドが発生する可能性がありますが、この点については矛盾する証拠があります。プログレッシブ モードでは「PNG 画像のサイズが約 20%、JPEG 画像と GIF 画像のサイズが約 10% 増加する」と Jeff Atwood 氏は主張しています。ただし、Stoyan Stefanov は、大容量のファイルの場合は(ほとんどの場合)プログレッシブ モードの方が効率的であると主張しています。
一見すると、最高品質の画像を可能な限り高速に提供するという観点からすると、プログレッシブ画像は非常に有望に見えます。追加のデータによって画質は向上しない(つまり、忠実度改善がすべてサブピクセルである)ことがわかったら、ブラウザは画像のダウンロードとデコードを停止できるという考え方です。
接続は簡単に終了できますが、再起動にはコストがかかる場合があります。画像が多いサイトでは、1 つの HTTP 接続を存続させ、できるだけ長く再利用するのが最も効率的な方法です。1 つの画像のダウンロードが完了したために接続が早期に終了した場合、ブラウザは新しい接続を作成する必要があります。これは、低レイテンシ環境では非常に遅い場合があります。
この問題を回避する 1 つの方法は、HTTP Range リクエストを使用することです。これにより、ブラウザは取得するバイト範囲を指定できます。スマート ブラウザは、HEAD リクエストを送信してヘッダーを取得し、処理して、実際に必要な画像の量を決定してから取得できます。残念ながら、HTTP 範囲はウェブサーバーで十分にサポートされていないため、このアプローチは実用的ではありません。
最後に、このアプローチの明らかな制限は、読み込む画像を選択せず、同じ画像の忠実度が変わるだけです。そのため、この機能は「アート ディレクション」のユースケースには対応していません。
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
を一致させることもできます。関連するメディアクエリは device-pixel-ratio で、予想どおり、最小値と最大値のバリエーションが関連付けられています。高 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>
を設定することはできず、画像はすべて背景付きの要素である必要があります。最後に、デバイスのピクセル比に厳密に依存すると、ハイ DPI スマートフォンが EDGE 接続中に巨大な 2x 画像アセットをダウンロードする結果になる可能性があります。これは最適なユーザー エクスペリエンスではありません。
新しいブラウザ機能を利用する
最近、高 DPI 画像の問題に対するウェブ プラットフォームのサポートについて多くの議論が行われています。Apple は最近、この分野に参入し、image-set() CSS 関数を WebKit に導入しました。そのため、Safari と Chrome の両方がサポートしています。CSS 関数であるため、image-set()
は <img>
タグの問題に対処しません。「@srcset」と入力します。この問題は対処されますが、このドキュメントの執筆時点ではリファレンス実装はまだありません。次のセクションでは、image-set
と srcset
について詳しく説明します。
高 DPI をサポートするブラウザ機能
最終的にどの方法を採用するかは、特定の要件によって異なります。ただし、前述のすべてのアプローチには欠点があります。ただし、将来的には、image-set
と srcset が広くサポートされれば、この問題に対する適切な解決策になるでしょう。当面は、理想の未来にできるだけ近づけるベスト プラクティスについて説明します。
まず、この 2 つの違いは何ですか?image-set()
は CSS 関数で、background CSS プロパティの値として使用できます。srcset は <img>
要素に固有の属性で、構文は似ています。どちらのタグでも画像宣言を指定できますが、srcset 属性を使用すると、ビューポートのサイズに基づいて読み込む画像を構成することもできます。
画像セットのベスト プラクティス
image-set()
CSS 関数は、-webkit-image-set()
という接頭辞で使用できます。構文は非常にシンプルで、1 つ以上の画像宣言をカンマで区切って指定します。画像宣言は、URL 文字列または url()
関数で構成され、その後に関連する解像度が続きます。例:
background-image: -webkit-image-set(
url(icon1x.jpg) 1x,
url(icon2x.jpg) 2x
);
これにより、2 つの画像から選択できることがブラウザに通知されます。1 つは 1x ディスプレイ用に、もう 1 つは 2x ディスプレイ用に最適化されています。ブラウザは、さまざまな要素に基づいてどちらを読み込むかを選択します。ブラウザが十分にスマートであれば、ネットワーク速度も考慮されます(現在のところ実装されていません)。
ブラウザは、適切な画像を読み込むだけでなく、それに応じて画像をスケーリングします。つまり、ブラウザでは 2x の画像のサイズは 1x の画像の 2 倍の大きさと仮定し、2x の画像を係数 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
);
上記のコードは、image-set をサポートするブラウザで適切なアセットを読み込み、それ以外の場合は 1x アセットにフォールバックします。ただし、image-set()
をサポートするブラウザは少ないため、ほとんどのユーザー エージェントでは 1x のアセットが読み込まれる点に注意してください。
このデモでは、image-set()
を使用して正しい画像を読み込み、この CSS 関数がサポートされていない場合は 1x アセットにフォールバックします。
ここで、image-set()
をポリフィル(JavaScript シムを作成)して、それで終わりにしない理由について説明します。結局のところ、CSS 関数に効率的なポリフィルを実装するのは、非常に困難です。(理由の詳細については、この www スタイルのディスカッションをご覧ください)。
画像の srcset
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-HD.jpeg が配信されます。
画像要素に image-set を使用する
ほとんどのブラウザでは img 要素の srcset 属性が実装されていないため、img 要素を背景付きの <div>
に置き換えて、image-set アプローチを使用するのが魅力的かもしれません。これは機能しますが、注意点があります。欠点は、<img>
タグには長いセマンティック値があることです。実際には、これは主にウェブクロールとユーザー補助の理由で重要です。
-webkit-image-set
を使用する場合は、background CSS プロパティを使用することをおすすめします。この方法の欠点は、画像サイズを指定する必要があることですが、1x 以外の画像を使用している場合は不明です。代わりに、次のように content 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 を使用してフォールバックします(上記参照)。
- 画質を犠牲にできる場合は、圧縮率の高い 2x 画像の使用を検討してください。