­

margin 重疊問題深入探究

  • 2020 年 10 月 28 日
  • 筆記

margin 重疊問題 Margin Collapse

塊的上外邊距(margin-top)和下外邊距(margin-bottom)有時合併(重疊)為單個邊距,其大小為單個邊距的最大值(或如果它們相等,則僅為其中一個),這種行為稱為邊距重疊。

MDN-外邊距重疊

重疊的結果:

1、兩個相鄰的外邊距都是正數時,重疊結果是它們兩者之間較大的值。
2、兩個相鄰的外邊距都是負數時,重疊結果是兩者絕對值的較大值。
3、兩個外邊距一正一負時,重疊結果是兩者的相加的和。
margin 重疊本質上的問題還是 BFC,要理解這個問題,我們先來看看重疊的條件。

重疊的條件

  • 必須是處於常規文檔流(非 float 和絕對定位)的塊級盒子,並且處於同一個 BFC 當中。
  • 沒有線盒,沒有空隙(clearance,下面會講到),沒有 padding 和 border 將他們分隔開
  • 都屬於垂直方向上相鄰的外邊距,可以是下面任意一種情況
    • 元素的 margin-top 與其第一個常規文檔流的子元素的 margin-top
    • 元素的 margin-bottom 與其下一個常規文檔流的兄弟元素的 margin-top
    • height 為 auto 的元素的 margin-bottom 與其最後一個常規文檔流的子元素的 margin-bottom
    • 高度為 0 或 auto 並且最小高度也為 0,不包含常規文檔流的子元素,並且自身沒有建立新的 BFC 的元素的 margin-top 和 margin-bottom

根據以上描述的條件,我們可以得出以下結論

  • 浮動元素不與任何元素的外邊距產生重疊(包括其父元素和子元素)
  • 絕對定位元素不與任何元素的外邊距產生重疊
  • 建立 BFC 的元素(例如浮動或 overflow 屬性的值不為 visible)不會與其在流中的子元素的外邊距重疊
  • inline-block 元素不與任何元素的外邊距產生重疊
  • 一個常規文檔流元素的 margin-bottom 與它下一個常規文檔流的兄弟元素的 margin-top 會產生重疊,除非它們之間存在間隙(clearance)
  • 一個不包含 padding 和 border 的父元素的 margin-top 與其第一個不包含 clearance 子元素的 margin-top 產生重疊
  • 一個 ‘height’ 為 ‘auto’ 並且 ‘min-height’ 為 ‘0’的常規文檔流元素的 margin-bottom 會與其最後一個常規文檔流子元素的 margin-bottom 重疊,條件為父元素不包含 padding 和 border ,子元素的 margin-bottom 不與包含 clearance 的 margin-top 重疊
  • 一個不包含border-top、border-bottom、padding-top、padding-bottom的常規文檔流元素,並且其 ‘height’ 為 0 或 ‘auto’, ‘min-height’ 為 ‘0’,其裡面也不包含行盒(line box),其自身的 margin-top 和 margin-bottom 會重疊。

Collapsing margins
總結一下就是

  • 浮動元素、絕對定位、inline-block元素不與任何元素的外邊距產生重疊
  • 不同 BFC 的元素不會產生重疊
  • 特殊情況下存在間隙(clearance)的元素不會產生重疊

我們來看看產生重疊的幾種情況

相鄰兄弟元素重疊

<div class="BFC">
  <div class="middle-box margin red"></div>
  <div class="middle-box margin green"></div>
</div>
<style>
  .BFC{
    display: flow-root;
  }
  .margin{
    margin:20px;
  }
  .big-box{
    width: 100px;
    height: 100px;
  }
  .middle-box{
    width: 50px;
    height: 50px;
  }
  .red{
    background-color: red;
  }
  .green{
    background-color: green;
  }
  .blue{
    background-color: blue;
  }
</style>

效果如下圖

我們通過absolute、inline-block、float可以使其margin不重疊



但是需要注意的是,使用absolute和float時,元素會脫離文檔流,需要對其定位做相應處理。
另外,由於這兩者脫離文檔流之後,如果後面還有兄弟元素,這個兄弟元素仍然會與前一個元素鄰接,也就是會繼續產生 margin 重疊

<div class="BFC relative">
  <div class="middle-box margin red"></div>
  <div class="middle-box margin green" style="position:absolute;">absolute</div>
  <div class="middle-box margin blue"></div>
</div>
<div class="BFC">
  <div class="middle-box margin red"></div>
  <div class="middle-box margin green" style="float: left">float</div>
  <div class="middle-box margin blue"></div>
</div>

效果如下圖

在這裡就必須提一下經常與 float 一起出現的 clear 屬性,這與我們上面提到的 間隙(clearance)也有關
clear 屬性規定元素的哪一側不允許其他浮動元素

描述
left 元素框的上外邊界低於這個元素之前的任何左浮動框的下外邊界
right 元素框的上外邊界低於這個元素之前的任何右浮動框的下外邊界
both 元素框的上外邊界低於這個元素之前的任何左浮動框或右浮動框的下外邊界
none 對浮動框沒有限制

clear 屬性設置了非 none 值後,就會產生間隙。這裡摘抄文檔中的原文,意思是間隙可以抑制重疊,並且充當元素的 margin-top

Values other than none potentially introduce clearance. Clearance inhibits margin collapsing and acts as spacing above the margin-top of an element. It is used to push the element vertically past the float.

看一個例子

<div class="BFC">
  <div class="middle-box margin green" style="float: left">float</div>
  <div class="middle-box margin blue" style="clear: both;"></div>
  <div class="middle-box margin red"></div>
</div>

效果如下,由於元素的 margin-top 會與 float 元素的 margin-bottom 相接,間隙就是元素 margin-top 到 float 元素 margin-top 的距離

其實這裡又隱式地產生了一個 margin 重疊,這不是套娃嗎!
所以關於兄弟組件的 margin 重疊問題,還是建議用新建一個 BFC 的方式來解決。
上面 float 產生的重疊也可以用 BFC 來解決。

<div class="BFC">
  <div class="middle-box margin red"></div>
  <div  class="BFC">
    <div class="middle-box margin green"></div>
  </div>
</div>

需要注意的是,建議新建 BFC 的時候使用 display:flow-root,意為創建一個行為類似於根元素的元素,這樣語義化更好

父子元素 margin-top 重疊

<div class="BFC">
  <div class="big-box margin green">
    <div class="middle-box margin red"></div>
  </div>
</div>

效果如下,子元素和父元素的 margin-top 重疊了

根據上面的總結,我們也可以在父子元素是上使用 inline-block、absolute、float 或在子元素上建立 BFC 來解決重疊問題
但這裡提供一種更好的方式,設置父元素的 padding 或 border

<div class="BFC">
  <div class="big-box margin green" style="padding: 10px">
    <div class="middle-box margin red"></div>
  </div>
</div>


這樣父元素子元素的 margin 也就不會重疊了
另外,你可以在父元素中填充內容來製造間隙,可以使用::before 或 ::after 偽元素,不過這樣會被內容佔掉一部分的容器高度

<div class="BFC">
  <div class="big-box margin green before">
    <div class="middle-box margin red"></div>
  </div>
</div>
<style>
  .before::before{
    content: '';
    display: inline-block;
  }
</style>


在實際使用中需要具體問題具體分析

auto 高度的父元素與子元素的 margin 重疊

<div class="BFC">
  <div class="auto-box margin green">
    <div class="middle-box margin red"></div>
  </div>
</div>
<style>
  .auto-box{
    width: 100px;
    height: auto;
  }
</style>

效果如下

解決的方式和上面的思路類似,這裡需要注意的就是由於父元素的高度是 auto,子元素脫離文檔流的情況需認真考慮
仍然推薦在父元素中添加 border、padding 屬性或在子元素上建立 BFC 的方式來解決這個問題

<div class="BFC">
  <div class="auto-box margin green" style="border: 10px solid;">
    <div class="middle-box margin red"></div>
  </div>
</div>

在查閱資料中發現可以通過設置 min-height 來解決 margin 重疊,其實本質上只是把父元素的內容撐開了。
我個人認為這沒有從根本上解決重疊的問題。

<div class="BFC">
  <div class="auto-box margin green" style="min-height: 100px">
    <div class="middle-box margin red"></div>
  </div>
</div>