TT Lab
はじめる
学ぶ 学習パス コース

CSS — 後に書いた規則が勝つ

300pxが300pxでない理由

TT Labで続きを見る

一言でいうと

width: 300pxは、標準の設定ではコンテンツ領域の幅であって、ボックスの幅ではありません。そして、上下のマージンは足されるのではなく相殺されます。この2つが、レイアウトが崩れる事故の大部分です。

なぜ必要なのか

「2つの要素を50%ずつ置いたのに、改行されてしまう」「パディングを入れたら、カードがグリッドの外にはみ出す」「上の要素にmargin-bottom: 20px、下の要素にmargin-top: 30pxを指定したのに、間隔が50pxではなく30pxになる」

3つとも、CSSを知らないからではなく、モデルを知らないまま値を入れたために起きることです。そして、3つとも、目で見ると「なぜこうなるのか」としか思えません。計算のルールを知っていれば、その場で答えが出ます。

どう動くのか: ボックスの4つの層

┌─────────── margin ───────────┐
│ ┌───────── border ─────────┐ │
│ │ ┌─────── padding ──────┐ │ │
│ │ │      content         │ │ │
│ │ └──────────────────────┘ │ │
│ └──────────────────────────┘ │
└──────────────────────────────┘

CSS 2仕様のボックスモデルが定義した4つの層です。標準のbox-sizing: content-boxでは、widthは、いちばん内側のcontentだけを指します。そのため、

.card { width: 300px; padding: 20px; border: 1px solid; }

このカードが占める横幅は、300 + 20 + 20 + 1 + 1 = 342pxです。親が300pxなら、はみ出します。

box-sizing: border-boxを指定すると、widthがborderまで含んだ値になります。上のカードはちょうど300pxになり、コンテンツ領域が258pxに縮みます。MDNのbox-sizingのドキュメントが、2つのモードを比較しています。

そのため、ほとんどすべてのプロジェクトが、いちばん上にこの3行を置きます。

*, *::before, *::after { box-sizing: border-box; }

marginは、どちらのモードでもwidthに含まれません。これだけは例外がありません。

マージンの相殺: 足されず、合わさります

上下に接したマージンは、足されずに、2つのうち大きいほうの値1つになります。MDNのマージン相殺のドキュメントが、3つのケースを整理しています。

  1. 隣り合う兄弟: 上の要素の下マージンと、下の要素の上マージンが合わさります
  2. 親と最初/最後の子: 間にボーダーもパディングもなければ、子のマージンが親の外へ漏れ出します
  3. 空の要素: 自分の上下のマージンが、自分同士で合わさります

2つ目が、最も人を戸惑わせます。親ボックスの内側に余白を与えようとして、子にmargin-topを指定したのに、親がまるごと下へ押されます。背景色があると、その色がついてこないので、さらにおかしく見えます。

相殺を止める方法は、間に何かを置くことです。親にpadding-topを1行、border-topを1行、またはdisplay: flow-rootです。新しいブロックフォーマッティングコンテキスト(BFC)を作ると、マージンは中に閉じ込められます。

そして、横方向のマージンは絶対に相殺されません。左右は、常に足されます。

flexとgridの中では相殺がありません

マージンの相殺は、通常のフロー(blockの書式設定コンテキスト)のルールです。flexアイテムとgridアイテムの間では起きません。

そのため、最近のレイアウトでは、相殺に出会う機会が減りました。その代わりに、gapがその場所を引き受けます。gapはアイテムの間にだけ入り、外側には入らないため、「最後の要素だけマージンを除く」というルールが、まるごと不要になります。

.list { display: flex; flex-direction: column; gap: 16px; }

この1行が、.list > * + * { margin-top: 16px }の代わりになります。

値はどこを基準に測るのか

パーセンテージは、包含ブロックの幅を基準にします。ここに落とし穴が1つあります。padding-topとmargin-topのパーセンテージも、幅を基準に測ります。高さではありません。縦横比を維持するボックスを作るときに、padding-top: 56.25%を使っていた古い手法は、ここから生まれました(今はaspect-ratioがあります)。

そして、min-widthとmax-widthは、widthよりもあとに適用されます。width: 100%とmax-width: 640pxを一緒に使うと、640pxを超えない可変ボックスになります。CSS Valuesが、単位と計算の順序を定義しています。

現場での姿

このサービスで実際に起きたことを、2つ書き留めておきます。

1つ目は、本文だけが画面の外にはみ出したことです。bodyをflex columnにして、本文にmargin: 0 autoを指定したところ、flexの子に自動マージンが付くと、引き伸ばし(stretch)が無効になります。幅の広いコードブロックが1つ入ってくると、本文だけがその幅に広がり、上部のバーとずれました。width: 100%を明示して直しました。

2つ目は、カードのグリッドが1行に2つしか入らなかったことです。width: 33.33%にpadding: 16pxを指定したのに、box-sizingが初期値のままでした。3つの合計が100%を超えて、改行されました。border-boxの1行で解決しました。

どちらも、計算のルールを知らないと「CSSがおかしい」で終わり、知っていれば、開発者ツールで計算された値を一度見れば済みます。

次のクイズで確認すること

box-sizingの2つのモードでボックスが実際に占める幅、マージンの相殺が起きる3つのケースと止める方法、そして、パーセンテージが何を基準に測るのかを確認します。