Android 4 に存在する device-width/height のバグ
Android 4 では、メディアクエリで用いられる device-width/height の値が、通常の CSS ピクセルではなく、デバイスピクセルとして与えられるバグがある。そのため Xperia Z など高解像度のディスプレイを持つ端末で、広い画面のために設計されたスタイルシートが適用されるといった問題が発生する。
CSSピクセル
高解像度のディスプレイの登場により、CSS で指定したピクセル数をそのままディスプレイのピクセルに当てはめると、物理的なサイズが小さくなりすぎてしまう。たとえば 72 ppi のディスプレイでは、16 px の文字は 16 pt の大きさとして表示されるが、264 ppi の解像度を持つ iPhone 4/5 のディスプレイではわずか 4.36 pt と、ルビ程度の大きさになってしまう。
そこで「CSS ピクセル」という仮想的な値が考えられた。実際のピクセルとの比を device-pixel-ratio という値で与えることにより、制作者が端末ごとに異なるピクセル密度を気にすることなく、Web ページをちょうどよい大きさに表示することができる。
CSS ピクセルとデバイスピクセル、device-pixel-ratio の値は次の関係にある。
CSS pixel = (device pixel) × (device-pixel-ratio)
たとえば画面の大きさが同じ iPhone 3GS と iPhone 4 では、CSS ピクセルとデバイスピクセル、device-pixel-ratio の値は次の通りである。
iPhone 3GS | iPhone 4 | |
---|---|---|
デバイスピクセル数 | 320 × 480 | 640 × 960 |
CSS ピクセル数 | 320 × 480 | 320 × 480 |
device-pixel-ratio | 1 | 2 |
device-width/height のとる値
iPhone OS 3 や iOS 4〜6、Android 2.3 では、device-width/height は CSS ピクセル数として扱われる。次のような CSS (あるいは HTML の link 要素の media 属性) で、画面の広さによってスタイルシートを切り替えることができる。
/* PC用の記述 */ @media (max-device-width: 768px) { /* タブレット端末用の記述 */ } @media (max-device-width: 480px) { /* スマートフォン用の記述 */ }
CSS において「px」という単位は CSS ピクセルを表す。だから、ここには CSS ピクセルが指定されるべきである。古い端末との互換性の面からも、ここは CSS ピクセルのほうがよい。
ところが Android 4 は、メディアクエリの記述においてのみ、「px」という単位を端末の物理的なピクセル数とみなしてしまう。とんでもないバグである。
メディアクエリでデバイスピクセルを扱う意義
もしかすると何らかの意図があって Android 4 ではデバイスピクセルとして扱っているのかもしれない。
しかしながら、デバイスピクセル数が重要になるのはラスタ画像を扱うときや、ヘアライン (印刷可能な最も細い線) を表現するときくらいのものだ (もしほかにデバイスピクセル数が重要になる場面があればご教示願いたい)。
それに、高解像度ディスプレイ用に高解像度のラスタ画像を用意する場合でも、device-pixel-ratio を参照すればよい。
結局は Android 4 の開発者が、device-width/height という名前に惑わされているだけのように思える。
このバグへの対処法
このように device-width/height をデバイスピクセル値として取り扱ってしまう Android 4 であるが、max/min-width/height の値は期待通り CSS ピクセルとして扱われる。
先ほどの例では max-device-width の代わりに max-width を使うことでこの問題を解決できる。
/* PC用の記述 */ @media (max-width: 768px) { /* タブレット端末用の記述 */ } @media (max-width: 480px) { /* スマートフォン用の記述 */ }
ただし PC でもブラウザのウィンドウの幅を狭めていくと表示が切り替わってしまう。しかしそれは稀なケースであろう。
3月1日追記:この方法は viewport=device-width を指定している場合でしか使えない。それ以外の場合は JavaScript を使って対処することとなる。