レスポンシブ対応のデバッグする際、デバイスの画面幅からはみ出して横スクロールの原因となっている要素がないかJavaScriptを使ってチェックしたい。
デバイスの画面幅の値を取得するには、最初、screen.width
やwindow.innerWidth
プロパティの値から取ればいいのかなと思っていましたが、それだと実行環境によってはチェックがうまくいかないケースがあるとわかりました。
この記事(前編)は、ブラウザーのレスポンシブデザインモード(RDM)と実機のブラウザーとでデバイスの画面幅がどのように認識されるか、その違いを調べたメモです。
レスポンシブモードと実機での表示の違い
例として、レイアウト幅1200pxのページを画面幅1024pxのiPad Proに表示した場合を取り上げます。
※サンプルのコードはこちら: https://codepen.io/kaz_hashimoto/pen/XWEXRdO
下図は左から順に、Chrome DevToolsからのデバイスモード、Safariのレスポンシブデザインモード、iOSシミュレーターを使って表示した画面です。
※画像引用:Photo by Inspirationfeed on Unsplash
デバイスモードやレスポンシブデザインモード(以下、この2つを総称してRDMと表記)で表示した場合、コンテンツが画面幅に収まらないため横スクロールが発生しています。一方、実機1で表示した場合はページ全体が縮小して画面の横幅にフィットして表示されました。
もう1つ大きな違いはメディアクエリの結果です(左端の矢印)。
クエリ | RDM | 実機 |
---|---|---|
@media (max-width: 1024px) |
true | false |
ブラウザーが認識している画面の寸法がRDMと実機とで異なるようです。そこで、このとき画面やビューポートの寸法に関するプロパティの値は何が返されるのかを検証してみました。
以下、デバイスのタイプはすべてiPad Pro (12.9-inch)を使用し、画面の方向は縦とします。デバイスの画面サイズは1024x1366です。また、HTMLにはビューポート属性が指定されているものとします。
<meta name="viewport" content="width=device-width, initial-scale=1">
結果
まずはデスクトップ版ブラウザー2のRDMで表示した場合です。表でセルの値が1024pxを超えるものを青色の背景で示しました。
Safariだけscreen.width
の値が2560pxになっていますが、これは私が使用しているモニター(iMac 27inch)の画面幅の値(cssピクセル単位)を返してきたようです。
一方、window.innerWidth
については、レイアウト幅の1200pxを返すものと、デバイスの画面幅1024pxを返すものが混在しています。
次に実機で表示した場合です。
iOS13以上ではページがフィットして表示されましたが、iOS12ではフィットせずに画面幅から溢れて横スクロールが発生しました。
2つの表からわかるように、iOS12を除き右端のセルの値は常に1024を表示しています。この計算式を使えば、RDMでも実機でも一律にデバイスの画面幅を取得することができそうです。
visualViewport.width * visualViewport.scale
iOSの場合、VisualViewportインターフェイスはiOS13からサポートされました。iOS12以前などvisualViewport
をサポートしていない古いブラウザーについてはwindow.innerWidth
で代用するしかないかと思います。
MDNの説明によると、window.innerWidth
の値はレイアウトビューポートの幅から取られるのが仕様なので、このサンプルの場合、1200pxを返すのが正しいと思われます。
他のケースで検証
式1で画面幅の値が得られるかどうか他のケースで検証してみましょう。
タブを追加した時のサイズ
iPadでタブを追加するとresizeイベントが発生します。下の動画では右側のconsoleに各プロパティの値がどう変化するかを表示しています。
See the Pen Vimeo Player API demo by Kazuhiro Hashimoto (@kaz_hashimoto) on CodePen.
consoleのログにはタイミングごとにラベルを付けました。
window.innerWidth
とvisualViewport.width
の値は共に最初1024pxに設定されますが、画像の読み込みが完了したタイミングで1200pxに変わります。そしてタブを追加してアクティブにした時にresizeイベントが発生し、一旦1024pxに戻った後、少し経ってから1200pxに変わる、という複雑な動きをしています。
一方、visualViewport.width * visualViewport.scale
は常に画面幅の1024pxが得られます。
Split View
次にiPadのSplit Viewで画面を分割した場合です。画面を縦置き・横置き両方やってみます。
まずはSafariのRDMです。SafariのRDMはiPadのSplit View表示もシミュレートできます。
screen.width
は相変わらずモニターの画面幅の値を返しますが、visualViewport.width * visualViewport.scale
は、Split Viewで分割された画面の幅が得られます。
次に実機でのSplit Viewです。
screen.width
はデバイスの画面幅、window.innerWidth
およびvisualViewport.width
はレイアウト幅(1200px)を返しています。こちらも同様にvisualViewport.width * visualViewport.scale
は、Split Viewで分割された画面の領域の幅が得られます。
まとめ
後で使うため、式1を関数で定義しておきます。
getDeviceWidth()
は、ビューポートのスケーリングの影響を取り除いた形でデバイスの画面幅の値(cssピクセル単位)を返します。
※Split Viewで画面を領域に分割している場合、この関数は領域の幅の値を返します
function getDeviceWidth() {
if (typeof window.visualViewport === 'undefined') {
return window.innerWidth;
}
const width = visualViewport.width * visualViewport.scale;
return Math.round(width);
}
後編の記事(2)へ続く。