JavaScriptでHTML要素の高さを取得する方法

JavaScript で HTMLの要素の高さ / 幅を取得しようと思った時、outer やら avail やら offset やら、オブジェクトにやたらとプロパティが生えていて混乱したのでまとめました。

## 検証環境

iMac / iPad / iPhone にて 2021年10月時点での Google Chrome 最新版で検証しました。デスクトップ版の Chrome は v94 でした。

プロパティを全列挙するとごちゃごちゃして見づらくなるため、高さに絞って説明しています。幅を得る時は Height を Width に読み替えてください。

## ディスプレイのサイズ - screen オブジェクト

デバイスのディスプレイのサイズを得るには [screen オブジェクト](https://developer.mozilla.org/ja/docs/Web/API/Window/screen) を参照します。

<img src="https://user-images.githubusercontent.com/15980747/137574117-69cebebd-497d-4dba-9a21-ea72d81fac93.png" width="700px">

```js
const h1 = window.screen.height;
const h2 = window.screen.availHeight;
```

**height** は画面の高さをピクセル数で表します。macOS の Dock を除いた領域は **availHeight** で得る事ができます。

## ブラウザのサイズ - window オブジェクト

ブラウザのサイズを得るには [window オブジェクト](https://developer.mozilla.org/ja/docs/Web/API/Window) を参照します。ブラウザが画面いっぱいに広がるタブレット・スマートフォンでは、ビューポートすなわち画面全体のサイズと同じです。

<img src="https://user-images.githubusercontent.com/15980747/137574221-6e5cb356-0c42-48f3-8459-1dbab1b6a676.png" width="700px">

```js
const h1 = window.outerHeight;
const h2 = window.innerHeight;
```

**outerHeight** はブラウザの高さそのものです。**innerHeight** はタブバーやブックマークバーを除いた HTML 文書の表示領域の高さです。

## HTML の領域のサイズ - document.documentElement オブジェクト

HTML の領域のサイズを得るには [document.documentElement オブジェクト](https://developer.mozilla.org/ja/docs/Web/API/Document/documentElement) を参照します。このオブジェクトはブラウザではルートの `<html>` 要素を示します。

<img src="https://user-images.githubusercontent.com/15980747/137574819-ed0704e8-9124-41ea-bc80-b6ae44f72bc3.png" width="700px">

```js
const h1 = document.documentElement.offsetHeight;
const h2 = document.documentElement.scrollHeight;
const h3 = document.documentElement.clientHeight;
```

図の緑色の枠は、文書などのコンテンツがレンダリングされた領域を示しています。

**offsetHeight** は線やスクロールバーを含む高さを整数で得る事ができます。コンテンツが伸びて縦スクロールが発生する場合は、伸びた分の高さがセットされます。

**scrollHeight** は線・マージン・スクロールバーを含まない内側のサイズです。コンテンツが伸びて縦スクロールが発生する場合は、伸びた分の高さがセットされます。

**clientHeight** は線・マージンを含まない内側のサイズです。コンテンツの伸び縮みは影響しません。

## コンテンツ領域のサイズ - body オブジェクト

コンテンツ領域のサイズを得るには [body オブジェクト](https://developer.mozilla.org/ja/docs/Web/API/Document/body) を参照します。

左の図は中身のコンテンツに合わせて body が伸び縮みする例を示しています。右の図は `height:100vh` など高さを制限した例を示しています。

<img src="https://user-images.githubusercontent.com/15980747/137583459-7e0db926-21c5-4da8-a271-30cff3fbd86e.png" width="700px">

```js
const h1 = document.body.offsetHeight;
const h2 = document.body.scrollHeight;
const h3 = document.body.clientHeight;
```

**offsetHeight** は線やパディングを含む高さを整数で得る事ができます。body の高さそのものを表した値で、中身のコンテンツがはみ出た分は考慮されません。

**scrollHeight** は線を含まない内側のサイズです。コンテンツが伸びて縦スクロールが発生する場合は、伸びた分の高さがセットされます。

**clientHeight** は線を含まない内側のサイズです。body の高さそのものを表した値で、中身のコンテンツがはみ出た分は考慮されません。

## サンプル

screen / window / document / body の数値を書き出すサンプルページを作ってみました。

https://f647x.csb.app/

手元の実機で試した結果は以下の通りです。

device | screen.height | screen.availHeight | window.outerHeight | window.innerHeight
--- | ---: | ---: | ---: | ----:
iMac 27 inch<br>全画面モード | 1,440 | 1,415 | 1,440 | 1,440
iPad Pro 12.9 inch 5th | 1,366 | 1,024 | 1,024 | 649
iPhone 7 Plus | 736 | 736 | 736 | 679

**iMac** は解像度と一致しました。
https://support.apple.com/kb/sp689?locale=ja_JP

**iPad** （横向きに置いています） は @GENZ_INC さんの [iPad画面サイズ、ピクセル数早見表](https://qiita.com/GENZ_INC/items/ad2e6eab4f6bba3fb6da) の「CSSピクセル」と一致しました。
<img src="https://user-images.githubusercontent.com/15980747/137575237-ae84dbec-b18b-4794-9c65-622033e32a2f.png" width="400px">
**iPhone** は @tomohisaota さんの [iPhone/iPad/Apple Watch解像度\(画面サイズ\)早見表](https://qiita.com/tomohisaota/items/f8857d01f328e34fb551) の「ポイント」と一致しました。
<img src="https://user-images.githubusercontent.com/15980747/137575291-5a57e2fd-9a57-4a68-8c32-fb3a4bb56c33.png" width="400px">
ブラウザの開発者ツールでデバイスをエミュレートすると、任意のサイズのディスプレイでブラウザを画面いっぱいに広げた扱いになるようです。
<img src="https://user-images.githubusercontent.com/15980747/137575472-665b3e63-6c69-47a0-b893-45bf9a5ccf8b.png" width="400px">

## 要素のサイズ - Element 基底クラス

HTML の要素の基底クラス [Element](https://developer.mozilla.org/ja/docs/Web/API/Element) は [getBoundingClientRect](https://developer.mozilla.org/ja/docs/Web/API/Element/getBoundingClientRect) メソッドを所有しています。このメソッドはビューポートからの位置のジオメトリである DomRect を返します。

<img src="https://user-images.githubusercontent.com/15980747/137576050-79cf5d80-c824-4f19-b607-2fa6f079312d.png" width="700px">

```js
const domRect = document.getElementById("foo").getBoundingClientRect();

/* domRect: DomRect
{
  x: 65
  y: 65
  width: 430
  height: 430
  top: 65
  right: 495
  bottom: 495
  left: 65
}
*/
```

**top** は要素の上位置を表します。**height** は線を含む要素そのものの高さです。**bottom** は要素の下端の位置を表します。

要素の中のコンテンツのサイズを得るには Element のプロパティを参照します。

<img src="https://user-images.githubusercontent.com/15980747/137582779-f0703bfe-2855-4af9-a034-bd4797e49852.png" width="700px">

```js
const h1 = document.getElementById("foo").offsetHeight;
const h2 = document.getElementById("foo").scrollHeight;
const h3 = document.getElementById("foo").clientHeight;
```

**offsetHeight** は線を含む要素そのものの高さを表します。この数値は getBoundingClientRect().height と一致します。

**scrollHeight** はコンテンツ全体の表示領域の高さを表します。コンテンツが伸びて縦スクロールが発生する場合は、伸びた分の高さも含みます。

**clientHeight** はコンテンツの表示領域のうち視認できる部分の高さを表します。

## まとめ

こうして整理してみるとなかなかに複雑ですね。outer / avail / offset などプロパティのプレフィクスは計算の意味を示したものとなっているようです。どのオブジェクトからどの数値を得るかを掴んでおけば混乱せずに目的の数値にたどり着く事ができそうです。