深入理解CSS margin折叠

1,805 阅读7分钟

什么是margin折叠?

W3C对于外边距叠加的定义

In CSS, the adjoining margins of two or more boxes (which might or might not be siblings) can combine to form a single margin. Margins that combine this way are said to collapse, and the resulting combined margin is called a collapsed margin.

大概意思是: 在CSS中,两个或多个毗邻的普通流中的盒子(可能是父子元素,也可能是兄弟元素)在垂直方向上的外边距会发生叠加,这种形成的外边距称之为外边距叠加。

Horizontal margins never collapse. 只有垂直方向的外边距会发生外边距叠加。水平方向的外边距不存在叠加的情况。

看例子,代码如下:

html

<div class="container">
    <div class="item"></div>
    <div class="item"></div>
</div>

css

.container {
    border: 1px solid red;
    width: 500px;
    margin: 0 auto;
}

.item {
    height: 60px;
    background: blue;
    width: 300px;
    margin: 50px auto;
}

效果如下图:

image.png

可以看出两item间隔为50px,即垂直方向上的外边距发生叠加,这就是所谓的margin折叠。

为什么会有margin折叠?

CSS1.0和2.0规范上描述:

CSS1.0

image.png

CSS2.0只有关于Collapsing margins描述:

image.png

CSS1.0中的规定写的很明白,是故意这样设计的,因为这样在大多数情况下符合平面设计师的要求。

参考深度剖析Margin塌陷,BFC,Containing Block之间的关系中:

在stackoverflow上Why the css specification...有大佬回答

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标签(上下都有相同的边距)就是如下图所示:

image.png

也就是P标签天生就长这样,也没有什么css来让你改他的默认样式。再来看看现在的P标签(其实就是用css修饰的一个display:block;元素)长什么样:

 p {
    display: block;
    margin-block-start: 1em;
    margin-block-end: 1em;
    margin-inline-start: 0px;
    margin-inline-end: 0px;
}

其中:

margin-block-start == margin-top

margin-block-end == margin-bottom

margin-inline-start == margin-left

margin-inline-end == margin-right

如果没有margin折叠,则表现为:

image.png

中间的间距就比开始个结束的要大,显然不好看(这就是css规范说的更符合设计师的设计)

什么时候会发生margin叠加

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
  • 都属于普通流的块级盒子且在同一个BFC中

  • 没有被paddingborderclearline box分隔开

  • 在垂直方向上是毗邻的,包括以下几种情况:

    • 盒子的top margin和它第一个普通流子元素的top margin

    • 盒子的bottom margin和它下一个普通流兄弟的top margin

    • 盒子的bottom margin和它父元素的bottom margin

    • 盒子的top marginbottom margin,且没有创建一个新的块级格式上下文,且有被计算为0的min-height,被计算为0或autoheight,且没有普通流子元素

    举个例子来说明:

    html:

<div class="container1"></div>
<div class="container2">
    <div class="item"></div>
    <div class="item"></div>
    <div class="item"></div>
</div>
<div class="container3"></div>

css:

.container1 {
    height: 20px;
    background: yellow;
    margin-bottom: 20px;
}

.container2 {
    margin: 10px 0 30px;
}

.container3 {
    height: 20px;
    background: green;
    margin-top: 100px;
}

.item {
    background: red;
    height: 20px;
    margin: 50px 0 -20px;
}

效果如下:

image.png

说明:

根据规则: 盒子的top margin和它第一个普通流子元素的top margin折叠, 所以 .container2和第一个.itemtop margin叠加,导致.container1.container2之间的边距为50px

根据规则: 盒子的bottom margin和它下一个普通流兄弟的top margin折叠, 所以.container2中的.item中的top marginbottom margin发生外边距叠加,它们之间的外边距为30px

根据规则: 盒子的bottom margin和它父元素的bottom margin折叠, 所以.container2中的最后一个.item发生bottom margin叠加,.container2.container3之间的边距为80px

总结来说:父子嵌套的元素垂直方向的margin取最大值。

垂直方向上毗邻的box不会发生折叠的情况

Adjoining vertical margins collapse, except:

  • Margins of the root element's box do not collapse.
  • If the top and bottom margins of an element with clearance are adjoining, its margins collapse with the adjoining margins of following siblings but that resulting margin does not collapse with the bottom margin of the parent block. 垂直方向上毗邻的box不会发生折叠的情况:
  • 根元素的外边距不会参与折叠
  • 一个有clearance的box的上下margin毗邻,它会与紧接着的下一个box发生margin折叠,但折叠后的margin不会再与它们父box的bottom margin折叠

如何计算折叠边距

In the case of negative margins, the absolute maximum of the negative adjoining margins is deducted from the maximum of the positive adjoining margins. If there are no positive margins, the absolute maximum of the negative adjoining margins is deducted from zero.

总结来说:

  • 两个相邻Box的margin外边距都是正数时,折叠外边距是两者中较大的值
  • 两个相邻Box的margin外边距都是负数时,折叠外边距是两者中绝对值较大的值
  • 两个相邻Box的margin外边距一正一负数时,折叠外边距是两者相加的和

如何避免外边距叠加

  • Margins between a floated box and any other box do not collapse (not even between a float and its in-flow children).
  • Margins of elements that establish new block formatting contexts (such as floats and elements with 'overflow' other than 'visible') do not collapse with their in-flow children.
  • Margins of absolutely positioned boxes do not collapse (not even with their in-flow children).
  • Margins of inline-block boxes do not collapse (not even with their in-flow children).
  • The bottom margin of an in-flow block-level element always collapses with the top margin of its next in-flow block-level sibling, unless that sibling has clearance.
  • The top margin of an in-flow block element collapses with its first in-flow block-level child's top margin if the element has no top border, no top padding, and the child has no clearance.
  • The bottom margin of an in-flow block box with a 'height' of 'auto' and a 'min-height' of zero collapses with its last in-flow block-level child's bottom margin if the box has no bottom padding and no bottom border and the child's bottom margin does not collapse with a top margin that has clearance.
  • A box's own margins collapse if the 'min-height' property is zero, and it has neither top or bottom borders nor top or bottom padding, and it has a 'height' of either 0 or 'auto', and it does not contain a line box, and all of its in-flow children's margins (if any) collapse.
  • 浮动元素不会与任何元素发生叠加,也包括它的子元素
  • 创建了BFC的元素(如浮动、overflow属性不为visible的元素)不会和它的子元素发生外边距叠加
  • 绝对定位元素和其他任何元素之间不发生外边距叠加,也包括它的子元素
  • inline-block元素和其他任何元素之间不发生外边距叠加,也包括它的子元素
  • 普通流中的块级元素的margin-bottom永远和它相邻的下一个块级元素的margin-top叠加,除非相邻的兄弟元素clear
  • 普通流中的块级元素(没有border-top、没有padding-top)的margin-top和它的第一个普通流中的子元素(没有clear)发生margin-top叠加
  • 普通流中的块级元素(height为auto、min-height为0、没有border-bottom、没有padding-bottom)和它的最后一个普通流中的子元素(没有自身发生margin叠加或clear)发生margin-bottom叠加
  • 如果一个元素的min-height为0、没有border、没有padding、高度为0或者auto、不包含子元素,那么它自身的外边距会发生叠加

以上内容如有问题,烦请各位大佬指教!多谢!

参考资料:

深度剖析Margin塌陷,BFC,Containing Block之间的关系

CSS1.0官方规范

CSS2.0官方规范

深入理解CSS外边距折叠(Margin Collapse)