边距与浮动元素一起折叠,为什么要添加额外的边距?

Margin collapsing with floated element, why there is an extra margin added?

2020 年更新:以下行为在最近的浏览器中不再可见


让我们从以下情况开始:

html {
  background: red;
}

body {
  margin: 0;
  min-height: 100vh;
  background-color: green;
}

div {
  min-height: 50px;
  background-color: pink;
  margin-bottom: 50px;
}
<div></div>

正文是用 min-height:100vh 定义的,我们有一个滚动条可以让我们看到 html。这里我们有一个 margin-collapsingdiv 的边距与主体边距一起折叠,因此在主体和滚动条之后创建这个 space .

如果我们参考 specification 我们有这种情况:

Two margins are adjoining if and only if:

...

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

div最后一个流入 元素,body 具有自动高度,因为我们只指定了最小高度。


现在让我们添加更多可能受此边距影响的元素并保持边距折叠规则。唯一的方法是添加 floated 元素,使我们的 div 始终是最后一个流入元素。

这是新代码:

html {
  background: red;
}

body {
  margin: 0;
  min-height: 100vh;
  background-color: green;
}

div {
  min-height: 50px;
  background-color: pink;
  margin-bottom: 50px;
}
.l {
  width:45%;
  height:50px;
  float:left;
  margin:0;
}
.r {
  width:45%;
  height:50px;
  float:right;
  margin:0;
}
<div></div>
<div class="l"></div>
<div class="r"></div>

我们可以清楚地看到,我们仍然有边距折叠(因为滚动)并且浮动元素也被向下推了相同数量的边距。

所以我的问题是:为什么会有这样的行为?


我对边距崩溃的理解是,最后我们只会在某处应用 一个 边距。通过添加新元素,我预计会出现以下两种情况之一:

  1. 添加浮动元素会以某种方式取消边距折叠(这不可能,因为我们没有违反任何规则)
  2. 浮动元素不会受到边距的影响,因为这个元素与主体边距一起折叠,因此 moved/applied 到主体。 (这对我来说是一个逻辑案例)

在规范中我还发现了这个复杂语句:

Note that the positions of elements that have been collapsed through have no effect on the positions of the other elements with whose margins they are being collapsed; the top border edge position is only required for laying out descendants of these elements.

我从上面了解到,其他元素不受边距折叠的影响,因此保持其初始位置,这解释了为什么浮动元素被向下推。 (顺便说一句我不确定是不是这样)


如果这就是解释,那么对我来说有点混乱和不合逻辑。我添加了一个边距,结果我得到了两个清晰可见的边距?

那么为什么会有这样的行为呢?或者也许我在规范中遗漏了一些东西,而我们面临的不仅仅是简单的边缘崩溃?


重要通知:在回答之前,请注意我不是在寻找解决这个问题或如何避免这个问题的方法。我知道至少有 5 种方法可以取消边距折叠(填充、溢出、边框、弹性框等)。我想了解为什么会发生这种情况。


供参考:这是由 where @Alohci 开始的,在我的回答中强调了这一点,经过一些评论后我们都没有被说服

在我开始之前,滚动条在所有浏览器(但 Firefox 除外)中呈现的问题是与此处询问的问题不同的问题。当父元素的 min-height 导致边距不相邻时,Firefox 不会折叠父元素与其子元素之间的边距。 It's also a known spec violation in Firefox that's being worked on and yet to be fixed.

现在,谈谈手头的问题。来自第 9.5.1 节(关于浮点数):

  1. A floating box's outer top may not be higher than the top of its containing block. When the float occurs between two collapsing margins, the float is positioned as if it had an otherwise empty anonymous block parent taking part in the flow. The position of such a parent is defined by the rules in the section on margin collapsing.

这句话的最后一句话很尴尬,但“规则”指的是(和link)崩溃的定义。虽然您从该部分引用的特定文本是相关的,但它并没有解释为什么浮动会尊重流入的边距 div

这样做:

If the top and bottom margins of a box are adjoining, then it is possible for margins to collapse through it. In this case, the position of the element depends on its relationship with the other elements whose margins are being collapsed.

  • [...]

  • Otherwise, either the element's parent is not taking part in the margin collapsing, or only the parent's bottom margin is involved. The position of the element's top border edge is the same as it would have been if the element had a non-zero bottom border.

注意最后一句话。如您所知,具有非零底部边框会取消边距折叠。这意味着浮动的位置就好像流入 divbody 元素的底部边距 没有 折叠,导致浮动看起来尊重流入的底部边距 div.

我如何判断浮动特别尊重流入的底部边距 div 而不是 折叠的边距?通过给 body 一个比流入 div 更大的底边距并观察它不会影响浮动的位置:

html {
  background: red;
}

body {
  margin: 0;
  margin-bottom: 100px;
  min-height: 100vh;
  background-color: green;
}

div {
  min-height: 50px;
  background-color: pink;
  margin-bottom: 50px;
}
.l {
  width:45%;
  height:50px;
  float:left;
  margin:0;
}
.r {
  width:45%;
  height:50px;
  float:right;
  margin:0;
}
<div></div>
<div class="l"></div>
<div class="r"></div>