CSSにおけるマージンの相殺を徹底解説
Post on:2021年1月21日
CSSにおけるマージンの相殺は何か、どういう条件で起こり、どのように相殺されるのか、また相殺を回避する方法などを紹介します。
下と上のマージンの相殺は単純ですが、マージン値が異なる場合、入れ子の場合、複数の場合、同方向の場合、負のマージンの場合、パディングやボーダーがある場合など、実装に伴うさまざまな例を解説します。
この記事を読むと、マージンの相殺がどのように機能するかがよく分かるようになり、悩むことはなくなります。
The Rules of Margin Collapse
by Josh W. Comeau
下記は各ポイントを意訳したものです。
デモは元記事ではインタラクティブですが、Gifアニメにしています。
※当ブログでの翻訳記事は、元サイト様にライセンスを得て翻訳しています。
- はじめに
- 垂直マージンのみが相殺される
- 隣接する要素のみが相殺される
- より大きなマージンが勝つ
- 入れ子は相殺を防ぎません
- マージンは同じ方向にも相殺される
- 3つ以上のマージンも相殺される
- 負のマージンによる相殺
- 複数の正と負のマージン
- 相殺はフローレイアウトの場合のみ
- CSSの旅は続く
はじめに
CSSでは隣接するスペースが重なってしまう場合があります。この現象はマージンの相殺と呼ばれるもので、正しく理解していないと混乱するかもしれません。
マージンの相殺の実際の例を見てましょう。
2つのp要素を兄弟にして、上下にマージンを与えます。
1 2 3 4 5 6 7 8 |
<style> p { margin-top: 24px; margin-bottom: 24px; } </style> <p>Paragraph One</p> <p>Paragraph Two</p> |
マージンの相殺の実際の例
p要素の間のマージンは上下の24pxを合算した48pxではなく、実際には上下の24pxは統合され、24pxになります。
この例だけであれば、単純だと思うかもしれません。しかし、CSSを書いていると、マージンが期待通りでなかったり、予期せぬ方法で崩れたりして驚くかもしれません。実際のプロジェクトでは、あらゆる種類の状況が問題を複雑にします。
幸いなことに、この悪名高いメカニズムの背後にあるルールを理解してしまえば、明確になり、驚くことはなくなります 👍🏼
この記事では、マージンの相殺を深く掘り下げ、理解していきます。
もう戸惑うことはなくなります!
垂直マージンのみが相殺される
CSSの仕様にmargin-collapseが追加されたときにCSS設計者は、水平マージンは相殺されるべきではない、という奇妙な選択をしました。
初期の頃は、CSSはレイアウトに使用することを目的としてなかったため、仕様書を書いている人たちは、カラムやサイドバーではなく、見出しと段落を想定していたからです。
マージンの相殺の1つ目のルールは、垂直マージンのみが相殺されることです。
垂直マージンのみが相殺され、水平マージンは相殺されない
コードで見てましょう。
2つの兄弟のp要素の左右にマージンを与えます。
Writing modes
CSSでは、ブロックレベルの要素が垂直ではなく水平に積み重なるように、Writing modesを切り替えることができます。その場合、マージンの相殺にどのように影響を与えると思いますか?
ブロック要素が水平に積み重なると、マージンの相殺は反転します。つまり、水平マージンは相殺されますが、垂直マージンは相殺されません。
したがって、さきほどの1つ目のルールは正確ではなく、ブロック方向のマージンのみが相殺されるとするのが正確です。
ただし、歴史的に縦書きのWebページは比較的まれです。英語のWebページが普遍的であると決めつけないことは重要ですが、上から下へのテキストよりも右から左へのテキストにフォーカスを当たるべきです。
隣接する要素のみが相殺される
ブロック要素間のスペースを増やすために、<br>タグを使用するのはやや一般的です(※スペースのために<br>タグを推奨するものではありません)。
2つの兄弟のp要素間に、<br>タグを加えてみます。
1 2 3 4 5 6 7 8 9 |
<style> p { margin-top: 32px; margin-bottom: 32px; } </style> <p>Paragraph One</p> <br> <p>Paragraph Two</p> |
残念ながら、これはマージンに悪影響を及ぼします。
p要素間に、<br>タグを加える
<br>タグは空の不可視要素ですが、2つの要素間に他の要素がある場合はマージンが相殺されるのをブロックします。マージンが相殺されるには、DOM内で要素が隣接している必要があります。
より大きなマージンが勝つ
上下のマージンが同じ値の場合は統合され、同じ値になります。では、異なる値の場合はどうでしょうか?
例えば、上の要素に72pxのマージン、下の要素に24pxのマージンがあるとします。
上下のマージンの値が異なる場合
より大きなマージンが勝ちます。
マージンを「パーソナルスペース」と考えると、これは直感的に感じられます。
ソーシャルディスタンスを2メートルに保つことは、社会的責任です。もし誰かがもっと広いスペース、例えば3メートルが必要な場合は、お互いのパーソナルスペースの要件を満たすために、3メートル離れる必要があります。
入れ子は相殺を防ぎません
ではここで実際のプロジェクトでありそうな、あらゆる種類の状況を見てましょう。下記のコードをご覧ください。
1 2 3 4 5 6 7 8 9 10 |
<style> p { margin-top: 48px; margin-bottom: 48px; } </style> <div> <p>Paragraph One</p> </div> <p>Paragraph Two</p> |
1つ目のp要素は入れ子になっていますが、マージンは相殺されます。
1つ目のp要素が入れ子になっている場合
どうしてこうなるのでしょうか?
マージンがどのように機能するか誤解しているかもしれません。
マージンは兄弟間の距離を広げることを目的としています。つまり、子と親間の距離を広げるためのものではありません。子と親間は、パディングです。
マージンはたとえそれが親要素にマージンを移すことを意味するとしても、常に兄弟間の距離を増やそうとします。この場合、子要素の<p>ではなく、親要素の<div>にマージンを適用した場合と同じ効果が得られます。
「でも、そんなはずはない」「マージンで親と最初の子の間の距離を広げたことがある」というあなたの声が聞こえます。
マージンが親に移される(そして折りたたまれる)ために満たされなければならないいくつかの条件があります。
- 要素間に他の要素がない(<br>については前述のルールを参照)。
- 親要素にはheightが定義されていない。
- 親要素には該当する辺に沿ったパディングやボーダーがない。
特に最後の条件は非常に一般的であるため、簡単な例を見てましょう。この場合、入れ子になっている子は次の段落のマージンを組み合わせることはできません。
該当する辺にパディングがある場合
パディングとボーダーは一種の壁と考えることができます。2つのスペースの間にある場合、邪魔になる壁があるため、マージンは相殺されません。パディングやボーダーのサイズは関係ありません。1pxでもパディングがあれば、マージンの相殺を妨げることになります。
マージンは同じ方向にも相殺される
これまで見てきたすべての例は、隣接する反対方向のマージン(上要素の下マージンと下要素の上マージン)でした。
驚くべきことに、マージンは同じ方向でも相殺されます。
同じ方向のマージンが相殺される例
コードは、下記の通りです。
1 2 3 4 5 6 7 8 9 10 11 |
<style> .parent { margin-top: 72px; } .child { margin-top: 24px; } </style> <div class="parent"> <p class="child">Paragraph One</p> </div> |
これは前述のルールの延長線上にあると考えていいでしょう。子マージンは親マージンに「吸収」されています。つまりこの2つは結合され、これまでに見てきたマージンの相殺と同じルールに従っています(例えば、より大きいほうが勝つ)。
これは大きな驚きにつながるかもしれません。
一般的な例を紹介します。
このコードでは2つのセクションが接触しており、それぞれのコンテナの内側にマージンが適用されることを期待するかもしれません。
こんな感じに内側にマージンが適用される
しかし、<section>にはマージンが全くないので、これは合理的な仮定です! 実際には各ボックスの上部のスペースを増やして、段落に少し余裕を持たせることを目的としているようです。
問題なのは、0pxのマージンが相殺マージンであるということです。各セクションには0pxの上マージンがあり、それが段落上の32pxの上マージンと結合されています。そして、32pxの方がより大きいので、勝ちます。
マージンのブロック
このルールと前述のルールは、わたし達を油断させる傾向があります。
この挙動を無効にしたい場合はどうすればよいでしょうか?
CSSでは、display: flow-root;で要素の境界を強化できます。
この宣言は、ほとんどのHTML要素のデフォルト値であるdisplay: block;に似ていますが、新しいBlock Formatting Context(ブロックフォーマットのコンテキスト)を作成するという違いがあります。
CSSが加えられていない標準のHTMLドキュメントではBFC (Block Formatting Context) は一つしかなく、ドキュメントのルート(<html>タグ)に存在します。BFCとは何かを正確に説明するのは難しいのですが、典型的な「フロー」レイアウトのルールに従った要素のグループと考えることができます。
新しいBFCを作成するときは、ドキュメントをドキュメント内に埋め込むようなものです。そして、そのドキュメントの境界は頑丈な鋼です。マージンは通ることができません。
display: flow-root;はフロート要素も鋼の境界を通ることができないので、フロートをクリアするためによく使用されます。しかし、マージンが相殺されるのを防ぐために使用することができます。
1 2 3 4 5 6 7 |
.parent { display: flow-root; margin: 24px; } .child { margin: 24px; } |
display: flow-root;はすべての主要なブラウザでサポートされていますが、IEではサポートされていません。
3つ以上のマージンも相殺される
マージンの相殺は、2つのマージンだけに限定されません!
下記の例では、4つのマージンが同じスペースにあります。
4つのマージンの相殺
何が起きているのか理解するのは難しいかもしれませんが、基本的には前述のルールを組み合わせるだけです。
- 兄弟は隣接するマージンを組み合わせることができる(最初の要素にmargin-bottom、2番目の要素にmargin-top)。
- 親と子は同じ方向にマージンを組み合わせることができる。
各兄弟には、同じ方向のマージンを提供する子があります。
コードは下記の通りです。各マージンを確認してみてください。
<header>と<section>の間のスペースには4つの異なるマージンがあり、そのスペースを占めるために競合しています。
- headerは自身の下にスペースが必要。
- header内のh1には下マージンがあり、親と一緒に相殺される。
- headerの下のsectionは自身の上にスペースが必要。
- section内のpには上マージンがあり、親と一緒に相殺される。
最終的には段落の累積マージンが最大になるため、段落が優先され、<header>と<section>間のスペースは40pxになります。
負のマージンによる相殺
最後に、もう一つ考慮すべき要素があります。負のネガティブマージンです。
負のマージンを使用すると、2つの要素間のスペースを減らすことができます。これにより、子を親の境界ボックスの外に引っ張ったり、兄弟間のスペースを重なるまで減らすことができます。
負のマージンを使用すると、重ねられる
負のマージンはどのように相殺されると思いますか?
実は、正のポジティブマージンとよく似ています。負のマージンはスペースを共有し、そのスペースのサイズは最も重要な負のマージンによって決定されます。
下記の例では、大きい負のマージン(-75px)が小さい負のマージン(-25px)よりも重要であるため、2つの要素は75px重なります。
負のマージンによる相殺
では、負と正のマージンが混在している場合はどうでしょうか?
その場合、マージンは足し算されます。
下記の例では、負のマージン(-25px)と正のマージン(25px)は、-25px+25pxで0になるので、互いに打ち消しあって効果がありません。
負と正のマージンによる相殺
同じ値の場合は、マージンがない場合と全く同じです。
マージンがない場合と全く同じ
効果がないマージンを適用するのはなぜでしょうか?
例えば、2つのマージンのうちの1つを制御できない場合があります。定義済みのコンポーネントとして修正できないかもしれません。その場合、親に逆の負のマージンを適用することで、マージンを「キャンセル」できます。
もちろん、これは美しいコードではありません。マージンを追加するよりも、不要なマージンを削除した方がよいです。しかし、このテクニックは特定の状況下で救世主になるかもしれません。
複数の正と負のマージン
少し複雑になってきましたが、もう1つ確認しておくべきルールがあります。この記事の「ラスボス」で、これまで見てきたルールの集大成とも言えます。
同じスペースに複数のマージンが競合し、その中に負のマージンがある場合はどうなるでしょうか?
マージンが3つ以上ある場合、アルゴリズムは下記のようになります。
- 最大の正のマージンを見つける。
- 最大の負のマージンを見つける。
- この2つの値を合計します。
実際のコードで見てましょう。
上記のコードで最大の正のマージン値は、30pxです。そして、最大の負のマージン値は、-20pxです。
そして、この2つの値を合計すると、10pxになります。
※3Dのイラストはありません。マージンがあまりにも混沌としてるように見えてしまうので、明快さを提供できませんでした😅
相殺はフローレイアウトの場合のみ
ここまでの例はすべて「インフロー(in-flow)」を前提としています。CSS GridやFlexboxで配置しているものではありません。
要素をCSS GridやFlexboxで配置させたり、フローからはずしている場合(floatや絶対配置など)はマージンが相殺されることはありません。これはフルブリードレイアウトのようなテクニックと組み合わせると素晴らしい結果が得られます。フローレイアウトではない場合は、マージンの代わりにgapを使用することをお勧めします。
実際のところ、デベロッパーの間ではマージンを使用するのはなく、レイアウトコンポーネントを選択する動きが活発化しています。
参考: Margin considered harmful
レイアウトコンポーネントは確かに素晴らしいと思いますが、もちろんマージンは普遍的なものであることも認識しています。それを見越したとしても、使用するプロダクト、または使用するデベロッパーと一緒に取り組む必要がある可能性があります。
CSSの旅は続く
ルールがいっぱいでしたね!
しかし、少し実践すれば、自然と覚えます。すぐにマージンの相殺がどのように機能するかが分かるようになり、考える必要はなくなります。
この記事は私が近々開講する「JavaScriptデベロッパーのためのCSS講座」から抜粋したものです。講座では、パズルのようにミニゲームで直感力を養い、動画でレイアウトを構築する方法を解説します。
CSSについても、マージンの相殺だけではなく、CSSの達人になるために知っておくべきことを解説します。興味のある方はぜひチェックしてみてください!
sponsors