viewport、お前誰や

# viewport、お前誰や

## 初めに
Qiitaでは始めまして。Sippoです。  
もうすぐクリスマスですね。世間ではアドベントカレンダーの季節らしいです。
去年はあまりサークル活動に積極的でなく、全く関与しなかったアドカレですが、今年こそはと意気込んで参加させていただきました。

## 何をしたの？  

まずは、この記事を書くに至った経緯を説明させてください。
受注していたweb制作のバイトでcssのメディアクエリを使ってレスポンシブ対応をしなくてはならなかったのですが、その時に
`<meta name="viewport" content="width=device-width"/>`を書かないとモバイル用のページが表示されないという現象にぶち当たりました。  
まあ、よく考えたら当たり前のことだった(最後に一応書いておく)のですが、せっかくなので色々調べてみました。  
この記事に概要として、以下に今回僕が何をしたのかを記しておきます。  
- MDNに助けを求めた
- htmlの仕様書を見に行った
- cssの仕様書を見に行った
- Chromiumのソースコードを解説してもらった
- 実験して確かめた


## viewportとは(MDNに助けを求めた)
解説に入る前に一応確認しておきましょう。  
[MDN](https://developer.mozilla.org/ja/docs/Web/HTML/Viewport_meta_tag)によると、  
> ブラウザーのビューポートは、ウェブコンテンツを見ることができるウィンドウの領域です。これはレンダリングされたページと同じサイズではないことが多く、その場合、ブラウザーはユーザーがスクロールしてすべてのコンテンツにアクセスできるよう、スクロールバーを提供します。

> モバイル端末やその他の狭い画面では、通常画面よりも広い仮想ウィンドウまたはビューポートでページをレンダリングし、レンダリング結果を縮小して、すべてを一度に見ることができるようにするものがあります。ユーザーは、パンやズーム操作によって、ページのさまざまな領域を見ることができます。例えば、モバイル画面の幅が 640px の場合、ページは 980px の仮想ビューポートでレンダリングされ、 640px の空間に収まるように縮小されるかもしれません。  
(MDNより)


僕の理解としては、  
「『ピクセル』と一口に言ってもcssで```width="100px"```って書くのと、物理的に存在する、ディスプレイを構成する単位の『ハード的なピクセル』って違うよね？  
でも、それをいちいち考えるのって大変だから、一旦**仮想的に**```viewport```ってものを定義して、その中でレイアウトを考えよう。  

ってものだと思ってます。

## htmlの仕様書を見に行く 
参考  
- [htmlの仕様書（日本語訳）](https://momdo.github.io/html/)
- [原文](https://html.spec.whatwg.org/multipage/)  

僕は英語を読む気力はなかったので日本語訳を読みましたが、より正確な情報を求める方は原文の方を読みに行くことを強くお勧めします。 
仕様書によると、metaタグの`name`属性に指定することのできる「標準メタデータ名」には、以下のものがあるらしいです。  
- application-name 
- author
- description
- generator
- keywords 
- referer
- theme-color 
- color-scheme  

・・・おや？  
お気づきでしょうか。そう、viewportくんがいませんね。  

仕様書には「標準メタデータ名」以外に「`他のメタデータ名`」という項目があり、
> 誰でも自由にWHATWG Wiki MetaExtensions pageを編集して、いつでもメタデータ名を追加できる。新しいメタデータ名は、次の情報とともに指定することができる  
とのことです。  

つまり、viewportは「誰かが作った」metaタグの拡張。
「他のメタデータ名」は重複不可で、一覧は[ここ](https://wiki.whatwg.org/wiki/MetaExtensions)を参照するらしい。  
見に行くと、(リンクはもう切れてましたが)cssの定義に飛ばすような言及が見つかったので、cssの仕様書を見に行くことにする。  

## cssの仕様書を見に行く　　
残念ながら、W3Cのcss仕様書には日本語訳がありません。仕方ないので、原文を読みに行きます。  
- [cssの仕様書](https://www.w3.org/TR/css-device-adapt-1/#at-ruledef-viewport)  

お、ありましたね。viewport。  
というわけで、viewportは「css(というと語弊があるが)が作ったmetaタグの拡張」ということが判明しました。  

仕様書では、
> <meta>タグの内容は初めに、ブラウザによって`@viewport`に解釈される。  
と明言されており、「ほー、本当にcssが担当なんだな」と実感しました。  

せっかくなので、少し中身を見てみることにする。  

metaタグのviewportに認識されるプロパティは以下の6つ。
- width 
- height 
- initial-scale  
- minimum-scale  
- maximum-scale  
- user-scaleble  
これら以外のプロパティは無視され、特にエラーを吐いたり、変な挙動を示すというわけではないようです。  


## やってみる……の前に(Chromiumのソースコードを解説してもらった)
私も今回知らなくて検証の際にめちゃくちゃ混乱したのですが、
知り合いのつよつよエンジニア様によると、  
viewportは少なくともChromiumにおいてはモバイルにおいてのみ有効みたいです。
その根拠が[Chromiumのソースコード](https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/inspector/dev_tools_emulator.cc;l=227?q=setviewportenabled&ss=chromium%2Fchromium%2Fsrc:third_party%2Fblink%2F)になるのですが、詳しいことはご本人が別途記事を書いてくれているので、[そちらの記事](https://qiita.com/karintou8710/items/55d4f7ec4f101cba7a05)を参照してください。  

軽くだけ書いておくと、  
ソースコード(コピペ)  
```c
void DevToolsEmulator::SetViewportEnabled(bool enabled) {
  embedder_viewport_enabled_ = enabled;
  bool emulate_mobile_enabled =
      device_metrics_enabled_ && emulate_mobile_enabled_;
  if (!emulate_mobile_enabled) {
    web_view_->GetPage()->GetSettings().SetViewportEnabled(enabled);
  }
}
```  
はっきりと見つけたわけではないですが、こんな処理を書いている以上、さすがに違いはあるのでしょう。  
なので、検証する際にはdevtoolでモバイルモードに切り替えておく必要があります。  


##  やってみる1
`viewport`が物理的なピクセル数を無視して、仮想的にピクセル数を設定してくれるなら、次のコードはページを横方向に4分割してくれるはず！！  
ならないよって人はdevtoolでエミュレータが起動してるかに注意！！(僕はこれで何度も混乱しました) . 

```html
<!DOCTYPE html>
<html>

    <head>
        <meta name="viewport" content="width=100"/>
    </head>
    
    <body>
         <div id="one"></div><div id="two"></div><div id="three"></div><div id="forth"></div>
    
    </body>

    <style>
        body{
            margin:0;
        }

        div {
            width: 25px;
            height: 20px;
            display: inline-block;
        }

        #one {
            background-color: red;
        }
        #two {
            background-color: green;
        }
        #three {
            background-color: blue;
        }
        #forth {
            background-color: yellow;
        }
    </style>
</html>

```  
軽くコードの解説をしておくと、  
初めにviewportを100に指定。viewportはcssピクセルを意味するので、それぞれのdivタグを25pxずつに設定しておけば、綺麗に4分割してくれるというわけです。  
`inline-block`を指定しているのは、`div`タグデフォルトの`block`要素だと改行されてしまうから。  
`height`を指定しているのは中に何も書かない要素を作成しているので、そのままだと高さが0になってしまい表示されないから。  

ちなみに、`div`タグ毎に改行して4行で書くと、それぞれの`div`タグの間にスペースが生まれて綺麗に分割されなくなるので注意しましょう。  


結果は  
![test1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1903651/4c62e56c-ad4f-67dc-b609-16499a60b83d.png)
よし、オッケ。



## やってみる2
jsを叩くことで確認してみる。  
[MDN](https://developer.mozilla.org/ja/docs/Web/API/Window/innerWidth)によると、`window.innerWidth`がviewportを参照してくれるらしい。(が、これだと想定している挙動とならなかったので、html要素の横幅、つまりコードとしては`document.documentElement.clientWidth`も併用することにします)  

とりあえず、4パターンでの検証結果。

1.viewport未指定 && 横幅80px  

```html
<!DOCTYPE html>
<head>
    <!-- 何も書かない -->
</head>

</html>
```  

```js
document.documentElement.clientWidth
>>> 980
window.innerWidth
>>> 320
```

2.viewport未指定 && 横幅1500px  
```html
<!DOCTYPE html>
<head>
    <!-- 何も書かない -->
</head>

</html>
```  

```js
document.documentElement.clientWidth
>>> 1500
window.innerWidth
>>> 1500
```  

3.viewportを100pxに指定 && 横幅80px
```html
<!DOCTYPE html>
<html>
    <head>
        <meta name="viewport" content="width=100"/>
    </head>    
    <body>
    </body>
</html>
```  

```js 
window.innerWidth
>>> 100 
document.documentElement.clientWidth
>>> 100
```
4.viewportを100pxに指定 && 横幅1500px 

```html 
<!DOCTYPE html>
<html>
    <head>
        <meta name="viewport" content="width=100"/>
    </head>    
    <body>
    </body>
</html>
```  

```js 
window.innerWidth
>>> 300 
document.documentElement.clientWidth
>>> 300
```

うーん。ちょっと思った通りにならない。  
この数字を全部完璧に説明することはできませんが、ある程度の説明はできるかもしれないヒントがMDNに書いてありました。  

> ブラウザーは要求された縮尺で画面を埋めるために必要であればビューポートの幅を拡大させます。これは、特に大画面の端末で使用する場合に有用です。
> 画面幅が 500 ピクセル以上ある場合、ブラウザーは画面に合わせるために（ズームインするのではなく）ビューポートを拡大します。
> この仮想ビューポートは、モバイル端末に最適化されていないサイト全般を、画面が狭い端末でも見やすくするための方法です。  

つまり、viewportは本来、モバイルなどの横幅が小さい端末での使用を想定しており、指定したviewport以上の横幅を持つメディアでは、(指定されたviewportでレンダリングしてズームインするのではなく)適切にviewportそのものを拡大して表示する。 
だから、viewport(default値が980)未指定の時は、横幅980px以下では980pxのviewportを確保してくれますが、980pxを超えると自動で拡大されて980よりも大きなviewportとなる。  
同様に、viewportに100を指定した時は、横幅100px以下では100pxのviewportを確保してくれますが、100pxを超えると自動で拡大されて100より大きなviewportとなる。  
ということなのでしょう。

うーん、それなら！

## やってみる3 
viewportが自動拡大された場合は、レイアウトはどうなるのか。  
つまり、たとえばviewportを100pxに指定して、横幅1500pxで表示。すると、先ほどの実験の通り、viewportは自動拡大されて100pxより大きな値となる。  
で、ここでたとえばcss`width=25px`と指定していたものはちゃんと画面を4分割したサイズになるのか。それとも拡大されたviewportに従って、それより小さく表示されるのか。  
```html
<!DOCTYPE html>
<html>

    <head>
        <meta name="viewport" content="width=100"/>
    </head>
    
    <body>
         <div id="one"></div><div id="two"></div><div id="three"></div><div id="forth"></div>
    
    </body>

    <style>
        body{
            margin:0;
        }

        div {
            width: 25px;
            height: 20px;
            display: inline-block;
        }

        #one {
            background-color: red;
        }
        #two {
            background-color: green;
        }
        #three {
            background-color: blue;
        }
        #forth {
            background-color: yellow;
        }
    </style>
</html>
```
結果：  
![test3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1903651/af0e1a9d-2a7f-2308-9bc0-296f3d0b1d45.png)

```js
window.innerWidth
>>> 300 
document.documentElement.clientWidth
>>> 300
```
うーん、これは拡大されたviewportでレンダリングされてますね。  
4つ合わせてちょうど画面全体の1/3ほどのサイズ。  
レンダリングまでは指定したviewport幅でしてくれてたらコードが書きやすいかなと思っていましたが、ダメみたいです。  

ということで、指定したviewport幅以上のメディアで表示した場合には、拡大されたviewport幅でレンダリングが行われます。


## やってみる4　
今度は指定するviewportの最大値と最小値を探ってみる。  
それこそ仕様書を読めと言われる気がしなくもないですが、まあ実験に勝る証拠はないでしょうの精神でやっていきます。  

これに関しては簡単で、viewportに極端な値を指定してみます。  

- viewportに100000を指定
```js
window.innerWidth
>>> 276
document.documentElement.clientWidth
>>> 10000
```
どうやら、上限値は10000っぽい……？

- viewportに10001を指定
```js
window.innerWidth
>>> 276
document.documentElement.clientWidth
>>> 10000
```
うん、おそらく10000ですね。  
そして、やはり`window.innerWidth`はviewportの指定とはあまり関係がなさそうですね。  

- viewportに1を指定。 && 横幅1pxで表示  
下限値を探る際は、横幅が大きいとviewportも自動拡大されてしまうので、注意しなくてはいけません。  
```js 
window.innerWidth
>>> 1 
document.documentElement.clientWidth
>>> 1
```
おー。下限値は特になさそう。  


## viewportの指定をしないとメディアクエリが効かないのはなんで？  

数ヶ月前の私が出会った、
`<meta name="viewport" content="width=device-width"/>` 
を書かないと、cssのメディアクエリが効かない現象。  
ここまで読んでくださってる聡明な読者の皆様(言いたかっただけ)はお分かりかと思いますが、viewportのデフォルト値は980です。  
だから、パソコンだろうがスマホだろうが、横幅1pxのメディアだろうが、viewportの指定をしない場合は横幅980として扱われます。  
だからメディアクエリで横幅を取得して分岐を書いても、横幅には常に980が入ってくるため、分岐が使われることはありません。
以上。

## 最後に 
ここまで駄文・長文にお付き合いいただきありがとうございました。  
記事を書くことで今までなんとなくで使っていたviewportに関する知識が整理された気がします。
それでは！