HTMLの、ブロック要素サイズの留意点

## はじめに

HTMLのブロック要素と width とかとの関係について、注意すべき点をまとめた説明が少ないようなので、浅くとも広くここに書いてみます。

このドキュメントでは記述を簡単にするため、横方向のみについて書きます。

また、この記事中でのサイズの指定は、全て px を使います。
実際には auto 指定やパーセント指定も多く使いますが、これらのテクニックはそれだけで大きな話になってしまうので、他の記事に譲ります。

同じく簡素化のため、margin, border, padding は(上下左右の)単一指定のみ使います。下記文章中で時々「×2」が出てくるのは、左右で同じ値を使っているためです。

## margin, border, padding, width・height の関係

基本的な事ですが、margin, border, padding, width の関係は、以下のようになっています(box-sizing が未指定の場合)。

![margin,border,padding,widthの関係](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/181201/5757e0d8-1d09-4f5c-b96b-1ad02a69f881.png)

CSSで記述する「width」は、一番内側の要素(content)のサイズのことです。

上図を見ても分かるとおり、ブロック要素全体のサイズは、左右 margin と左右 border、左右 padding、width のサイズの合計になります。
そして、このブロック要素全体の位置指定を、left や top でおこなうことになります。

margin、border や padding の初期値は 0 なので、何も指定していなければ、要素全体のサイズと width が一致します。
しかし実際のレイアウトでは margin や padding を多用するので、要素を入れ子にした場合、スペースが広がりがちになります。
margin と padding のどちらを使うかというのは、border や background があるときは明確ですが、それ以外に auto 指定をどこでやるべきかという話も絡んできます。このドキュメントでは追求しないので、別途調べてください。

余談になりますが、多くのブラウザには開発者ツールが備わっていて、要素の大きさを調べることが出来ます(例えば Chrome だと、「表示」メニュー → 「開発 / 管理」 → 「デベロッパーツール」を使います)。
開発者ツールで要素(Elements)を選ぶと要素のサイズが表示されますが、この時のサイズは margin を除外したサイズが表示されます。
これは、上で説明した「要素全体のサイズ」とは意図が違うので注意してください。
お手軽なのでつい見てしまうのですが、このドキュメントで説明する使い方には向きません。

## 横並びや縦並び

要素を親子関係にしようとする時には、親のサイズをきちんと計算してあげないと、意図通りの表示にならないことがあります。

例えば、border や padding 指定を含む span ブロックを横に2つ並べ、その2つをくっつけた状態で全体的にセンタリングしたい時は、以下のように span の要素サイズを2倍した値を、親(ここでは div.parent)の width に指定することになります。
親の width に大きめな値を書いて誤魔化すと、中央より若干左側に寄った表示になってしまいます。

```css
span {
	display: inline-block;
	margin: 4px;
	border: 2px solid gray;
	padding: 10px;
	background-color: Aqua;
	width: 200px;
	height: 60px;
	/* この span ブロック全体の横幅は、(4+2+10)*2 + 200 = 232px */
}

div.parent {
	background-color: violet;
	margin: auto;	/* 左右の margin が最大まで広がるので、センタリングになる */
	width: 464px;	/* span を2つ並べるので、span全体横幅の2倍を指定 */
}
```

```html
<div class="parent">
 <span>左側のコンテンツ
 </span><span>右側のコンテンツ
 </span>
</div>
```

なんだか html が見にくいですが、span を1行ごとに分けて書いてしまうと、2つの span の間に意図しない隙間が入ってしまう(改行コードがスペースに変換されてしまう)ので、こういう風に書いて対処しています。

また、この div.parent では「margin:auto」としているので、padding は指定していません(もし padding を指定しても横方向はセンタリングのままで変化せず、縦方向だけが広がったように見えます)。
勿論、div.parent 内部に別の要素(例えば文字など)を加えたい場合は、padding 指定を書いた方が良いでしょう。

## background-size との関係

background で塗られる範囲は、border の内側です(最初の図を確認してみてください)。

このため例えば background-color を使って四角形を描く時、四角形の大きさを width・height に設定して padding も設定してしまうと、思ったより大きな範囲が塗られてしまうことになります。
塗られる大きさを正確に指定したくて padding も使いたい時は、width・height には padding×2 を引いた値を設定すれば良いことになります。

background-color の四角形を正確なサイズにしたいという人は少ないかも知れませんが、background-image を使う時は正確なサイズにしたいと思うでしょう。

背景画像として設定すると言う事は要素の内側に何かしら表示したい訳で、すると padding 指定が必要になります。
padding が 0 で無いという事は、width・height を変更するしかありません。
そして、background-image でも、background-color の時と同様な計算をすれば良いのです。

下記の例では、大きさが 200x60 の画像を background-image に使っています。

```css
div.test {
	margin: 4px;
	border: 2px solid gray;
	padding: 10px;
	background-image: url(img_200.jpg);	/* 画像サイズは 200x60 */
	background-repeat: no-repeat;
	background-size: 200px 60px;
	width: calc(200px - 10px * 2);	/* 画像width - padding×2 */
	height: calc(60px - 10px * 2);	/* 画像height - padding×2 */
}
```

background-size にはオリジナルの画像サイズを書いて、width・height は padding×2 を引いた値を設定しています。
こういう時、calc は役立ちますね。ブラウザの対応状況は[コチラ](https://caniuse.com/calc)です。
背景画像を拡大縮小したくない場合は、この計算方法を使えば良いです。

背景画像を拡大縮小した方が良い場合は、background-size に cover や contain の値を指定します。
この時、border の内側と画像の縦横比率が違う時は、画像をトリミングするか、隙間が出来るかの選択をしなければいけません。
大ざっぱに説明すると、

- cover は、内側に画像を隙間無く表示しようとしますが、内側に収まらない部分が**トリミングされます**。
- contain は、画像全体をトリミングせずに表示しようとしますが、内側の余った部分は background-color で塗られます(**隙間が出来ます**)。

背景画像を拡大縮小するにしても、画像はトリミングされたくないし、要素ブロックに余白も作りたくない場合は、以下のような計算をすれば良いことになります(横幅を基準にした場合)。

```
画像比率 = (width + padding×2) ÷ 画像width
height = 画像height × 画像比率 - padding×2
```

(background-size は、cover か contain のどちらでも良い)

## box-sizing: border-box を使う時

ここまでの説明は、box-sizing が content-box (デフォルト値) での話でした。

box-sizing が content-box の時は、一番内側の content で確保したい大きさを width・height に書けば良いので、内部に画像を置いてどちらかの幅をピッタリ合わせたい時などは分かりやすかったです。

しかし一方、border や padding の値を変えただけでブロック要素全体のサイズが変化してしまい、その変更が外側にどんどん伝搬してしまう(画面全体に影響が出ることもある)という使いにくさもありました。
レイアウトを試行錯誤している最中は内側にあるブロックほど変更をしたくなるのに、内側にあるブロックほど変更の影響が大きく出てしまうのです。

そういう時は、box-sizing:border-box を指定すると、変更が楽になります(IE8以上で[使えます](https://caniuse.com/?search=box-sizing))。

![border-box 指定時の width](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/181201/450f82a4-f02d-2dde-be74-43dc71e35e62.png)

上図の通り、box-sizing:border-box では、width と height が、border の外側サイズ指定になります。

box-sizing:border-box は、padding の変更が外側に及ばなくなるだけでなく、全体をパーセンテージ指定にするときにも使いやすいです。
しかし、box-sizing の無指定(content-box)と border-box を入り交じりで使うと、メンテナンス性が悪くなるように思います。使うなら全ての div に適応とかした方が良いかもしれません(邪道？)。

border-box 指定を使えば padding サイズが気にならなくなるので background-image が楽になりそうですが、border も併用する時は注意が必要です。
border-box は「border の外側までのサイズ」ですが、background は「border の内側のサイズ」だという微妙な違いがあるためです。
border 付きで背景画像を表示したい時は、width に 「background-size + border」を設定してやらないと、トリミングされてしまいます。

## ハマり所

以上いろいろ書きましたが、結局、ハマり所はどこなのかというのをまとめてみます。

個人的に「ブロック全体のサイズ」で一番ハマったのは、ブラウザの開発者ツールです。
ツールでブロックを選択すると、margin 部分まで色を変えて教えてくれて、しかもサイズ表示が margin の外に付くのです。
こんな表示されたら、margin の外側のサイズを表示しているのだと勘違いしてしまいますよね。ご注意ください(私も昔、数時間を無駄にしました)。

もう1つのハマり点は、改行コードのスペース化けです。
改行を除去するツールもありますが、人間が読める HTML では無くなってしまいますね。
かと言って、改行を除去する以外の対処方法は無いようです(?)。
content 内の改行をスペース化しない CSS 指定とかあれば良いのですけどね。

background-image も、サイズの考え方が独特になるので注意が必要ですね。
background-image を使うよりも、普通に div の中で position:relative などを使って要素を重ね合わせた方が、構造は複雑になりますが結果的にはメンテしやすくなるように思います。
メディアクエリでの画像の切り替えなども、img の方がやりやすいですしね。

## おわりに

「包含ブロック」という言葉があるのですが、「ブロック要素全体」とは微妙に定義が違うみたいなので、ここでは使いませんでした。ブロックとボックスの違いなどもちょっと曖昧です。すみません。

文章中の間違いや補足などがありましたら、コメントにて指摘して頂ければ幸いです。