以前学习HTML和CSS的时候,确实听到关于margin的塌陷与合并问题,当时也没注意,后来自己开始写页面的时候才发现这确实是个恼人的问题,就决定写这一篇类似于笔记的文章记录一下(因为我的博客根本就没人看)

Margin合并

现象

首先假设我们有两个div元素
一个用红色填充,一个用蓝色

<div style="height: 100px;width: 100px;background-color: red;"></div>
<div style="height: 100px;width: 100px;background-color: blue;"></div>

然后我们再给红色div加上
margin-bottom: 100px;
此时,两个div元素在竖直上的距离为100px
这都没问题,此时图像应该是这样:

但我们突然想要给蓝色的div加上
margin-top: 100px;
使他们之间的距离有200px
但此时,问题出现了,他们之间的距离仍然没变
好像两个margin合并了

<div style="height: 100px;width: 100px;background-color: red;margin-bottom: 100px;"></div>
<div style="height: 100px;width: 100px;background-color: blue;margin-top: 100px;"></div>

由来

CSS1.0的时候 Margin塌陷就出现了
那么问题来了
Margin Collapse (外边距叠加)到底是人为的还是无意形成的呢
答案是。。CSS1.0就是故意这么干的
w3.org上写的很清楚

Two or more adjoining vertical margins (i.e., with no border, padding or content between them) are collapsed to use the maximum of the margin values. In most cases, after collapsing the vertical margins the result is visually more pleasing and closer to what the designer expects. In the example above, the margins between the two 'LI' elements are collapsed by using the maximum of the first LI element's 'margin-bottom' and the second LI element's 'margin-top'. Similarly, if the padding between the 'UL' and the first 'LI' element (the "E" constant) had been zero, the margins of the UL and first LI elements would have been collapsed.

我在w3.org上找到了原文,有兴趣的可以自己去看看 传送!

那么问题又来了,为什么CSS1.0开发的时候要故意导致Margin Collapse呢?

In most cases, after collapsing the vertical margins the result is visually more pleasing and closer to what the designer expects.

大多数情况下,塌陷了垂直方向的margin,在视觉上会更好而且更接近于设计师的期待。。。(英语渣,反正大概是这个意思)
可以看出这是个十分令人疑惑的解释,为啥你觉得Margin塌陷了就好看呢。。这不是给代码添麻烦嘛
后来我在另一篇文章上找到了结果
一样迷惑的作者去StackOverFlow上问了这个问题,一位老码农回答了这个问题
传送门

There's a historic reason. Mostly about how P(Paragraph) elements were rendered in the days before CSS. CSS needed to replicate the existing behaviour. But the behaviour still makes sense today for the reasons given in the question

简单点说,就是在CSS1.0出现之前,就有P标签了。CSS出现后,P标签有了CSS样式,默认样式如下

可以看到,p标签的上下都是有1em的margin的,如果没有margin合并,那就会导致连续的几个p标签间距会有2em,很明显不是我们想要的,而且与CSS出现之前的p标签的表现不符,所以CSS编写的时候考虑到这种情况,就决定特殊对待,Margin Collapse就出现了。

但是Margin Collapse既然是特殊情况,就要有条件触发

Two margins are adjoining if and only if:

both belong to in-flow block-level boxes that participate in the same block formatting context

no line boxes, no clearance, no padding and no border separate them (Note that certain zero-height line boxes (see 9.4.2) are ignored for this purpose.)

both belong to vertically-adjacent box edges, i.e. form one of the following pairs:top margin of a box and top margin of its first in-flow child

bottom margin of box and top margin of its next in-flow following sibling

bottom margin of a last in-flow child and bottom margin of its parent if the parent has 'auto' computed height

top and bottom margins of a box that does not establish a new block formatting context and that has zero computed 'min-height', zero or 'auto' computed 'height', and no in-flow children

A collapsed margin is considered adjoining to another margin if any of its component margins is adjoining to that margin.

只要破坏了这几个条件,Margin Collapse就不会触发

解决

关于垂直方向Margin合并的问题。。。可以说根本不用解决
只要给一个元素加上margin-top或者margin-bottom,另外一个不加上margin就自然不会出现Margin Collapse

<div style="height: 100px;width: 100px;background-color: red;"></div>
<div style="height: 100px;width: 100px;background-color: blue;margin-top: 200px;"></div>

在上面的例子中,只需要把下面一个div的margin-top改成200px,自然就达到了我们的目的

Margin塌陷

由来

其实这里的Margin塌陷翻译上才更接近Margin Collpase
其实这里的塌陷原因就是上面的由来
Margin Collapse不仅发生在两个相邻元素上,在父子结构上的margin一样会合并导致塌陷

表现

现在我们想要一个div里面再加上一个div,使子元素的div在父元素的右下角

<div style="height: 200px;width: 200px;background-color: red;">
        <div style="height: 100px;width: 100px;background-color: orange;margin-left: 100px;"></div>
    </div>

加上margin-left后,子元素轻松地被移到了右上角,似乎接下来只需要加上margin-top: 100px;就可以达到我们的目的了

此时,奇怪的事情出现了,子元素确实如我们所愿向下移动的100px,但是。。。。父元素同样跟着一起向下移动了100px,好像子元素上面的margin塌陷了一样

这就是CSS中的Bug——Margin塌陷

解决

这回的解决方案不能和刚才一样草率了,因为刚刚Margin合并的方法在这里毫无用处

那么,就要有请CSS2.0的新特性,BFC( Block Format Context ),块级格式化上下文。

在CSS2.0中,同样有着Margin Collapse的存在,然后因为Margin Collapse也会导致不少的问题,所以CSS2.0的开发人员就想了一个办法:再制定一个特殊的条件,触发了这个条件,CSS会有稍稍的不同,会导致相邻的两个元素的margin被隔离开来,不会导致Margin Collapse的触发。BFC就此诞生。
BFC的定义如下:传送门

Floats, absolutely positioned elements, block containers (such as inline-blocks, table-cells, and table-captions) that are not block boxes, and block boxes with 'overflow' other than 'visible' (except when that value has been propagated to the viewport) establish new block formatting contexts for their contents.

In a block formatting context, boxes are laid out one after the other, vertically, beginning at the top of a containing block. The vertical distance between two sibling boxes is determined by the 'margin' properties. Vertical margins between adjacent block-level boxes in a block formatting context collapse.

请注意,BFC并不是一个css属性,也不是一段代码,而是css中基于box的一个布局对象,它是页面中的一块渲染区域,并且有一套渲染规则,它决定了其子元素将如何定位,以及和其他元素的关系和相互作用。明确地,它是一个独立的盒子,并且这个独立的盒子内部布局不受外界影响,当然,BFC也不会影响到外面的元素。 (这段话来自 https://x-front-team.github.io/2017/02/19/CSS%E4%B8%AD%E7%9A%84BFC/ (这篇文章BFC介绍的十分详细,非常值得一读))

触发BFC有许多的条件,只要满足了其中之一,BFC就会被触发

  1. 根元素(整个页面就是一个大的BFC);
  2. float: left || right;
  3. overflow: hidden || auto || scroll;
  4. display: inline-block || table-cell || table-caption || flex || inline-flex;
  5. position: absolute || fixed;

当触发了BFC后,由于父元素和子元素同属于不同的BFC,所以就不会发生Margin Collapse了。

但是值得注意的是,触发BFC的每个条件基本都拥有自己的语义,要根据实际情况选择不同的条件来触发BFC,想上面的例子,由于子元素并没有超出父元素的范围,就可以加上overflow: hidden;来触发BFC

<div style="height: 200px;width: 200px;background-color: red;overflow: hidden;">
        <div style="height: 100px;width: 100px;background-color: orange;margin-left: 100px;margin-top: 100px;"></div>
</div>

就此成功解决

最后

十分感谢你能看到这里(我只是个萌新QAQ),希望这篇文章能对你有所帮助。由于本人水平有限,只能写成这样了,如有什么错误,请指出,谢谢。

顺带一提

由于本人水平有限,所以还上Google查了部分资料
这是我在文章里引用的出处
深度剖析Margin塌陷,BFC,Containing Block之间的关系 :文章里面的不少都是参考的这篇,但是我尽力写的更加通俗了,而且不少引用的W3C定义也是来自这里(但是我自己也去w3.org上找了)
CSS中的BFC:引用的较少,但是这篇文章确实不错


Maybe Some Days are Rainy,But My Heart is Luciferous!