viewは視覚、portは口。つまり、ビューポートは、そこを通して見る「フレーム」のような意味になります。「視界」そのものとも言えます。
ビューポートには、レイアウトビューポートとビジュアルビューポートの2つがあります。
レイアウトビューポートは、ドキュメントのレイアウトを決めるフレームで、ビジュアルビューポートは、ドキュメントの実際に見える範囲を決めるフレームです。
ビューポートは、ドキュメントをブラウザのコンテンツ表示領域へ適切に表示するための概念であるため、ブラウザのコンテンツ表示領域に大きく関係します。
まずはブラウザのコンテンツ表示領域から見ていきましょう。
ブラウザのコンテンツ表示領域
ブラウザウィンドウのコンテンツ表示領域(content area)は、ブラウザウィンドウの大部分を占めている部分です。(下図の黒色領域)
逆にブラウザウィンドウのコンテンツ表示領域外の枠部分(上図の白色部分/各種ボタン部分やバー部分も含む)のことを、専門用語でクローム(chrome)と言います。
正確には、user interface chrome 、あるいは browser chrome とも言います。chromeの語源は「色」ですが、多色の光沢を放つクロムという金属があって、他の錆びて変色する金属を覆うメッキ塗装剤に使われることから、不変の外側を表しているのだと思います。
なお、単にクロームと言ってしまうと、 一般的にはGoogle のアプリである Google Chrome のことと誤解されやすいので、注意が必要です。
最近のブラウザウィンドウは、左右のクロームをほとんど見えなくし、上下にだけクロームが見えるものが主流となっています。(下のクロームも、標準では見えなくなっています)
また、ブラウザウィンドウの仕様は、同じ種類のブラウザでも、マルチウィンドウ用とシングルウィンドウ用とで、異なります。
マルチウィンドウ(Windows、MacOS等)の場合
マルチウィンドウの場合、ブラウザウィンドウの形や大きさは、かなり自由に変えられます。もちろん、ほとんどゼロにしたり、スクリーンサイズよりも大きくしたりなど、極端な例は除きます。
スクロールバーは、コンテンツがコンテンツ表示領域に収まりきっている時は表示されませんが、コンテンツ表示領域からはみ出している時は表示されます。
マルチウィンドウでは、スクリーン全体のデバイス非依存ピクセル(dip)数がそれなりにある場合が多いので、クローム部分も常に見やすく表示されます。
スクリーン、デバイス非依存ピクセルなどの用語は、以下の記事でご確認ください。
シングルウィンドウ(iOS、Android等)の場合
シングルウィンドウの場合、ブラウザウィンドウの形や大きさは、あまり変えられません。
端末を回転させ、ブラウザウィンドウだけでなくスクリーン全体を、ランドスケープにするか、ポートレートにするか、の違いくらいでしょう。
スクロールバーは、スワイプしている時だけ表示され、操作していないときは表示されません。
また、コンテンツが表示領域からはみ出している時のスワイプでは、必ずスクロールバーが表示されますが、表示領域に収まりきっている時のスワイプでは、ブラウザによってスクロールバーを表示するかしないかが異なるようです。(表示領域に収まりきっていれば、本来は表示する必要もないはずですが…)
シングルウィンドウでは、スクリーン全体のデバイス非依存ピクセル(dip)数が少ない場合もあるので、クローム部分は操作によって非表示になったり、表示が変わったりします。
コンテンツ表示領域の縦横ピクセル数の値
ブラウザウィンドウのコンテンツ表示領域のサイズ(CSSピクセル数の値)は、DOM(Document Object Model)から、
- window.innerWidth ・・・ 横ピクセル数値
- window.innerHeight ・・・ 縦ピクセル数値
を使って求められます。(ブラウザによる暗黙のズームに注意)
なお、あなたが現在ご覧になっているブラウザでは、以下の値になっています。(±1程度の誤差が出る場合があります)
- window.innerWidth =
- window.innerHeight =
また、これらには約20px幅のスクロールバーも含まれます。
ドキュメント
ドキュメントは、ブラウザに映し出されるコンテンツ(contents)、およびそれに関係する情報=メタデータ(metadata) を記述したウェブページのソース(HTMLファイル等)から伝わる、ひとまとまりの情報です。(HTML: HyperText Markup Language)
詳しくは、以下の記事に書いてあるので、ここでは省略します。
レイアウトビューポート(layout viewport)
レイアウトビューポートは、ドキュメントをコンテンツ表示領域へレイアウトする際に基準となるフレームです。
レイアウトビューポートの幅
レイアウトビューポートの幅は基本的に、コンテンツ表示領域の幅と、ほぼ同じになります。
ただし、コンテンツ表示領域がスクロールバーを含むのに対し、レイアウトビューポートはスクロールバーを含みません。
マルチウィンドウの場合とシングルウィンドウの場合を、それぞれを詳しく見ていきましょう。
マルチウィンドウ(Windows、MacOS等)の場合
レイアウトビューポートの幅は、コンテンツ表示領域から縦スクロールバーを除いた幅になります。
よって、レイアウトによって縦スクロールバーが表示される場合、レイアウトビューポートの幅(CSSピクセル数)は縦スクロールバーの幅の分(20px程度)、コンテンツ表示領域の幅(CSSピクセル数)より小さくなります。
シングルウィンドウ(iOS、Android等)の場合
シングルウィンドウでは、スワイプ操作をしなければスクロールバーが表示されないため、レイアウトビューポートの幅は、コンテンツ表示領域の幅に一致します。
ただし通常、シングルウィンドウのコンテンツ表示領域には、暗黙のズームが働いているため、CSSピクセル数は常に980pxになります。(スクリーンの対角線インチ数が大きいタブレットなど、一部のシングルウィンドウでは、980pxより大きくなる場合もあります。)
レイアウトビューポートの高さ
レイアウトビューポートの高さは基本的に、コンテンツ表示領域の高さと、ほぼ同じになります。
ただし、コンテンツ表示領域がスクロールバーを含むのに対し、レイアウトビューポートはスクロールバーを含みません。
マルチウィンドウの場合とシングルウィンドウの場合を、それぞれを詳しく見ていきましょう。
マルチウィンドウ(Windows、MacOS等)の場合
レイアウトビューポートの高さは、コンテンツ表示領域から横スクロールバーを除いた高さになります。
よって、レイアウトによって横スクロールバーが表示される場合、レイアウトビューポートの幅(CSSピクセル数)は横スクロールバーの高さの分(20px程度)、コンテンツ表示領域の高さ(CSSピクセル数)より小さくなります。
シングルウィンドウ(iOS、Android等)の場合
シングルウィンドウでは、スワイプ操作をしなければスクロールバーが表示されないため、レイアウトビューポートの高さは、コンテンツ表示領域の高さに一致します。
ただし通常、シングルウィンドウのコンテンツ表示領域には、暗黙のズームが働いているため、CSSピクセル数は常に 980px / アスペクト比(横/縦) になります。
レイアウトビューポートの縦横ピクセル数の値
レイアウトビューポートのサイズ(CSSピクセル数)は、DOMから、
- document.documentElement.clientWidth ・・・ 横ピクセル数の値
- document.documentElement.clientHeight ・・・ 縦ピクセル数の値
を使って求められます。
なお、あなたが現在ご覧になっているブラウザでは、以下の値になっています。(±1程度の誤差が出る場合があります)
- document.documentElement.clientWidth =
- document.documentElement.clientHeight =
documentElementの意味と<html>のサイズ
document 直下の、documentElement は、HTMLドキュメント(<!DOCTYPE html>)下での、<html> 要素の代名詞です。
また、clientWidth と clientHeight は、要素の内側のCSSピクセル数を表します。
ただし、ルート要素である<html>の場合だけは特別に、これらが示す値は<html>の幅と高さではなく、レイアウトビューポートの幅と高さになります。
<html>の幅と高さ(CSSピクセル数)は、
- document.documentElement.offsetWidth・・・ 横ピクセル数の値
- document.documentElement.offsetHeight・・・ 縦ピクセル数の値
で求められます。
あなたが現在ご覧になっているブラウザでは、以下の値になっています。(±1程度の誤差が出る場合があります)
- document.documentElement.offsetWidth =
- document.documentElement.offsetHeight =
ドキュメントレイアウト
ドキュメントは、レイアウトビューポートを基準に、レイアウトされます。
レイアウトとは
レイアウトとは、位置と大きさが決められた状態のことです。
ドキュメントのレイアウトとは、ドキュメントを構成する全ての要素の位置と大きさを決めることです。
ドキュメントレイアウトの流れ
実際のドキュメントレイアウトでは、まずルート要素である<html>の幅が、レイアウトビューポートの幅に合わせられます。
そこから、<body>、<body>の子要素、・・・と、DOMツリーの順にレイアウトされていきます。
正確には、DOMツリーに似たレンダーツリーという樹形図が使われます。 レンダーツリーには、DOMツリーとCCSOMツリーが統合されています。
CCSOM: CCS Object Model ・・・これもDOMツリーとよく似た樹形図です。
<html>も<body>も、通常はブロックです。ブロックの幅は、直接指定されていなければ、親要素に合わせられます。
よって、レイアウトビューポートの幅に<html>の幅が合わせられ、<html>の幅に<body>の幅が合わせられます。
幅 | 高さ | |
---|---|---|
インライン (文字型) | 中身に合わせたCSSピクセル数になります。
| 中身に合わせたCSSピクセル数になります。
|
ブロック (段落型) | 親要素に合わせたCSSピクセル数になります。
|
ブロックの高さや、インラインの幅と高さは、子要素など、その中身に合わせられるので、DOMに沿って順にレイアウトが決まっていきます。
<body>の高さは、直接指定されていなければ、子要素など、その中身が全てレイアウトされてから、それらを包含する高さになります。
<html>の高さも、<body>まで全てのレイアウトが整ってから、それら全てを包含する大きさに決まります。
暗黙のズーム
注意しなければならないのは、シングルウィンドウでは、コンテンツ表示領域に暗黙のズームが使われるということです。
その暗黙のズームでは、ポートレートでもランドスケープでも、コンテンツ表示領域の幅(CSSピクセル数)が、常に 980px に固定されるように自動調整されます。
例えば、あなたが今、シングルウィンドウで見ているコンテンツ表示領域の暗黙のズーム倍率は、 倍です。
※ マルチウィンドウでは無意味な値になります。
このブラウザによる暗黙のズームが働くのは、特にシングルウィンドウで、かつコンテンツ表示領域の横dip数が 980 dip未満の場合です。
実際には、よほど対角線インチ数の大きなシングルウィンドウスクリーンでなければ、横dip数が 980 dip 以上になることはないでしょう。
たとえデバイスピクセル数が大きくても、その分通常は、デバイスピクセル比も大きく設定されるからです。
また、対角線インチ数が大きくなれば、通常はマルチウィンドウスクリーンにするので、実質、全てのシングルウィンドウスクリーンで、暗黙のズームが使われます。
このようなデバイス非依存ピクセル数の少ないコンテンツ表示領域においても、横スクロールせずにコンテンツの全幅が表示されるべく、自動的に横dip数に 980 px が割り振られるようにズームアウトの倍率が計算されているのです。
この “980px” という数値は、何も特別な意味があるものではなく、このブラウザによる暗黙のズーム機能が作られた当時(2008年頃)の標準的なウェブページの全幅が入りきる幅から、時代が変わった今もずっと続いているだけものです。
ただし、このブラウザによる暗黙のズームによって計算される倍率は、通常、整数倍にはなりません。したがって、このブラウザによる暗黙のズームのおかげで、旧来のウェブページを横スクロールなしで見られるようにはなりましたが、その画質は劣化していることになるのです。
スクリーンサイズのCSSピクセルは別物?
ところで、コンテンツ表示領域のサイズ(innerWidth/innerHeight)は、本来、スクリーン全体のサイズ(screen.width/screen.height)以下になるはずなのですが、実際に数値を見比べてみると、そうはなっていない場合もあります。(特にシングルウィンドウの場合)
あなたが見ている今の環境 | 横CSSピクセル数 | 縦CSSピクセル数 |
---|---|---|
コンテンツ表示領域 | ||
スクリーン全体 |
これも、ブラウザによる暗黙のズーム(ズームアウト)の働きで、コンテンツ表示領域内のCSSピクセルが、スクリーン全体のサイズ測定に使われるCSSピクセルよりも小さくなっている場合があるためです。(同じサイズでも、単位が小さくなれば、数値は大きくなる理屈です。)
つまり、スクリーン全体のサイズ(screen.width/screen.height)は、ブラウザによる暗黙のズーム前のCSSピクセル単位(大抵はdip単位と同じ)ですが、コンテンツ表示領域のサイズ(innerWidth/innerHeight)は、ブラウザによる暗黙のズーム後の、それよりも小さいサイズの CSSピクセル単位であり、異なる単位であることに注意が必要なのです。
これらを同じ「ピクセル」だと思ってしまうと、矛盾した数値に見えてしまいますが、コンテンツ表示領域のサイズも、暗黙のズーム前の同じCSSピクセル単位で見る(コンテンツ表示領域のCSSピクセル数 × 暗黙のズーム倍率 を算出する)と、矛盾しない数値になります。
レスポンシブウェブデザイン
様々なスクリーンがある中で、同じ一つのウェブページをどのスクリーンでも見やすく、というのは少々無理があります。見やすい大きさにズームすると、今度は画質が犠牲になります。
かといって、スクリーン毎に別々のウェブページ(別々のドキュメント)を用意するというのも、管理上、限度があります。
では、画質を劣化させずに、見やすくレイアウトする方法はないのでしょうか?
スクリーン毎に別々のウェブページを用意しなくても、あくまでも同一のドキュメントで、スクリーンに合わせてデザインを変化させれば良いのではないでしょうか。
その発想がレスポンシブウェブデザイン(RWD: Responsive Web Design)という概念です。
そしてその解決策(実装方法)の一つが、ブラウザに暗黙のズームを使わせず、スクリーンサイズに合わせてうまくレイアウトを変えるメディアクエリです。
レスポンシブウェブデザインにおける、その他の解決策(その他の実装方法)としては、以下の方法などもあります。
- vw と vh というビューポート単位を使う方法(vw はレイアウトビューポートの幅、vh はレイアウトビューポートの高さです)
- JavaScriptで動的にデザインを変える方法
メディアクエリ
具体的には、次の2ステップになります。
- HTMLの<head>内に、<meta>で指定文を入れる。
- CSSでメディアクエリを指定する。
1. HTMLの<head>内に、<meta>で指定文を入れる。
HTMLの<head>内に、以下の一文を入れます。
<meta name="viewport" content="width=device-width, initial-scale=1.0">
これは、書き直すと、こんなイメージです。(実際にはこんなCSSはありません)
viewport {
width: device-width;
initial-scale: 1.0;
}
それぞれ、説明しましょう。
- width=device-width
- シングルウィンドウであっても、レイアウトビューポート幅を980pxではなく、スクリーンの横dip数と同じCSSピクセル数にします。
- initial-scale=1.0
- ズームの初期値、つまりユーザーがズームを変える前の暗黙のズーム倍率を1.0にします。
どちらも結局、コンテンツ表示領域内の 1 CSSピクセル(px)を、1 デバイス非依存ピクセル(dip)に割り振る指定なので、どちらか片方だけでも暗黙のズームを阻止できますが、様々な環境に合わせて念のために両方指定することが一般的になっているようです。
2. CSSでメディアクエリを指定する。
例えば、以下のように記述します。
@media screen and (max-width: 320px){
CSS規則
}
@規則
先頭の「@」は@規則の開始記号です。@規則は、通常のCSS規則とは別の特殊なCSS規則です。ここでは、続く「media」と合わせて「@media」で、条件付きグループ規則を記述できるようになります。
@規則には他に、外部のスタイルシートを読み込む「@import」、CSS アニメーションの中間ステップを記述する「@keyframes」などもあります。
条件付きグループ規則
条件付きグループ規則は、if文のようなもので、{}でグループ化されたCSS規則に、条件を付けられます。{}でグループ化されたCSS規則を、グループ規則と言います。「@media」は、メディアに関しての条件付きグループ規則なので、これをメディアクエリと言います。
メディア(media)は、「媒体」と日本語訳されることが多いですが、「情報を伝えるもの」ということで、スクリーンや紙や音響装置などを指します。クエリ(query)は、「情報を得るための問い」で、メディアが条件を満たすかどうかのクエリが、メディアクエリです。
条件
screen and (max-width: 320px) の部分が、その条件付きグループ規則の条件部分になります。この例では「メディアがスクリーン(プリンターやスピーカー等を除く)であった時」&「メディア幅 <= 320px」という条件を意味し、この条件を満たす時だけ、{}内のグループ規則に進み、この条件を満たさなければ、{}をスキップします。
【要注意】メディアクエリは必ず<meta>とセットで使う
このようなメディアクエリは、「1. HTMLの<head>内に、<meta>で指定文を入れる。」と必ずセットで指定する必要があります。
その理由は、「1. HTMLの<head>内に、<meta>で指定文を入れる。」がされていないと、暗黙のズームによって、いつもスキップされてしまうからです。
実際には 320dip しかない狭いスクリーンであっても、暗黙のズームによって、レイアウトビューポートの幅が 980px にされてしまうと、「メディア幅 <= 320px」の条件を満たさないと判定されてしまうのです。
CSSで直接dip単位を指定することはできません。
ここがユーザーによる意識的なズームと違い、暗黙のズームであるところの注意点です。
ビジュアルビューポート(visual viewport)
ビジュアルビューポートは、実際にユーザーに見えるドキュメントの範囲を決めるフレームです。
そしてビジュアルビューポートは、基本的にレイアウトビューポートと同じ領域を指します。
では、レイアウトビューポートとビジュアルビューポートで何が違うかというと、ユーザーズーム前の領域か、ユーザーズーム後の領域か、の違いです。
ユーザーズームは、レイアウトビューポートには影響しませんが、ビジュアルビューポートにだけ影響します。
なぜなら、ユーザーズームは、すでにレイアウトされたドキュメントを拡大したり縮小したり、ユーザーが扱いやすいサイズに調整してコンテンツ表示領域へ表示するためだけのものなので、それはドキュメントレイアウトに影響を与えないのです。
なお、ここで言っているユーザーズームには、ページズームを含みません。
ページズーム?
ページズームはユーザーズームであっても、レイアウトに影響を与えてしまうズームです。
ページズームは、ブラウザの設定で倍率指定するズームで、ページズーム以外のユーザーズームは、ピンチ操作やダブルタップなどで行うズームです。
マルチウィンドウ(Windows、MacOS等)の場合
マルチウィンドウのマウス操作( Ctrl+ホイール など)では、たいていの場合、ページズームになります。
よって、ビジュアルビューポートは、レイアウトビューポートに一致します。
シングルウィンドウ(iOS、Android等)の場合
シングルウィンドウでのピンチズームやダブルタップズームは、ビジュアルビューポートを変化させます。
これらのズームでは、レイアウトビューポート内の操作した位置を中心に、CSSピクセルからdipへの割り当てを変化させます。ズームインでは、1dipに割り当てるCSSピクセル数を減らし、ズームアウトでは、1dipに割り当てるCSSピクセル数を増やします。
つまり、ユーザーズームでズームインをしても、コンテンツ表示領域の総dip数は変化しないので、ビジュアルビューポートの縦横CSSピクセル数は、レイアウトビューポートの縦横CSSピクセル数より少なくなります。
ビジュアルビューポートの縦横ピクセル数の値
ビジュアルビューポートのサイズ(CSSピクセル数)は、DOMから、
- window.visualViewport.width ・・・ 横ピクセル数
- window.visualViewport.height ・・・ 縦ピクセル数
を使って求められます。
なお、あなたが現在ご覧になっているブラウザでは、以下の値になっています。(±1程度の誤差が出る場合があります)
- window.visualViewport.width =
- window.visualViewport.height =
終わりに
ビューポートについては、あまり日本語での詳しい説明がなく、私自身も今回の執筆に際して、海外の文献を調べたり、実機やシミュレーターで実験したりで、かなり勉強になりました。
マルチウィンドウとシングルウィンドウでのブラウザ仕様の違いが、ビューポートの定義をわかりにくくしているところもあると思いますし、何よりも、異なる様々なズームが混在しているのに、それぞれズームの名前がはっきりしていないことが、ビューポートの各段階をわかりにくくしている一番の元凶だと思いました。
そこで、この記事では「暗黙のズーム」という表現を多用しました。明らかに実在しているのに、明確にされていない(名前もなければ認知も曖昧)という意味で暗黙なのです。正式名称ではありませんのでご注意ください。
もちろん、各ズームの正式名称の確認も試みましたが、ズーム(zoom)をキーワードに入れて検索すると、ミーティングアプリのズーム(Zoom)ばかりが出てきてしまうのも、厄介でした。
どなたか正式名称がわかったら、こっそり教えてください。(笑)