超级面板
文章目录
最新文章
最近更新
文章分类
标签列表
文章归档

外边距合并(margin collapsing)和 块格式上下文(Block formatting contexts)

外边距合并(margin collapsing)

在流式布局中,两个或者多个毗邻元素块的外边距(margin)在垂直方向上会被合并为单个的外边距,其大小是两个块中的最大的外边距,这就是外边距合并(margin collapsing

首先要注意的是,发生合并是元素块之间相互的行为,元素块数量则必须大于一个,单个元素块不存在合并的行为。其次,只有垂直方向上的 margin 会发生合并,水平方向上的并不会合并。

发生外边距合并这要有三种情况:

相邻的兄弟元素

两个相邻的兄弟元素会发生边距合并,如:

<div class="block">
<h2>相邻的兄弟元素会发生边距合并</h2>
<p style="margin-bottom: 40px;">下外边距被合并...</p>
<p style="margin-top: 20px;">...上外边距被合并。</p>
</div>

如上例,此时上下边距发生合并,距离为较大的一个边距 40px, 而不是 60px

块级父元素与其第一个或最后一个子元素

如果块级父元素中,不存在 border toppadding top,且其在普通流中第一个子元素不存在清除浮动这个属性(也即上边框宽度及上内边距距离为0时),此时这个块级父元素和其第一个子元素就会发生上外边距合并现象。此时这个父元素对外展现出来的外边距将直接变成这个父元素和其第一个子元素的 margin-top 的较大者。

同样的,块级父元素没有 border bottompadding bottominline contentheight=automin-heightmax-height 分隔时,就会与它的最后一个子元素的 margin-bottom 发生下外边距合并 。如下html和css:

html:

<div class="block1">
...
</div>
<div class="block2">
<h2>块级父元素与其第一个和最后一个子元素发生边距合并</h2>
<p class="p1">...</p>
<p class="p1 last">...</p>
</div>
<div class="block3">
...
</div>

css:

div.block2 {
margin: 10px 40px 10px;
}

.p1 {
height: 20px;
border: 1px solid #f00;
}
.block2 h2 {
margin-top: 30px;
}
p.last {
margin-bottom: 30px;
}

在上述示例中,block1block2>h2 元素之间的距离实际是 30px 而不是 30px + 10px;同样的 block2>p1.lastblock3 之间也是如此。

注意,在这种情况下,框的顶部边界被定义为父元素的边界,也就是说,block2 如果设置背景颜色,则背景不包括 h2 元素 margin-top10px ,此时等价于:

div.block2 {
margin-top: 30px;
}
.block2 h2 {
margin-top: 0;
}

空内容的块元素

如果一个块级元素内容为空,并且其 borderpaddinginline contentheightmin-height 都不存在,也即不存在任何能使元素高度大于0的属性,则此时其上下边距中间都没有阻碍,它的上下边距都会合并。

<div class="block">
<h2>空内容的块元素会发生边距合并</h2>
<p style="margin-bottom: 20px;">下外边距被合并...</p>
<p style="margin-top: 20px; margin-bottom: 20px;"></p>
<p style="margin-top: 10px;">...上外边距被合并。</p>
</div>

此时,中间的p元素将会被忽略,即使其指定了上下边距,但实际上,第一个 p 元素和第三个p元素的 margin 实际为较大的一个,即 20px,而不是 20px + 20px + 10px

负边距处理

当存在边距合并的两个元素中间有负边距,则实际边距是绝对值最大的那个负边距的绝对值减去绝对值最大的正边距,如果不存在正边距,则减去 0

也就是说当相邻元素的边距有一个为负边距时,合并后的外边距将是最大正边距加上最小负边距,即负边距中绝对值最大的一个。若两个都为负边距,则合并后的外边距将是即负边距中绝对值最大的一个。

<p class="p1" style="margin-bottom: 10px;">正边距1...</p>
<p class="p2" style="margin-top: -20px;background:green;">有一个负边距被求和合并</p>
<p class="p3" style="margin-top: 40px; margin-bottom: -20px;">正边距2...</p>
<p class="p4" style="margin-top: -10px;background:green;">都为负边距取最大绝对值合并</p>

在上述示例中,p1 与 p2之间的外间距为 (10px + (-20px)) 而 p1 与 p2 之间的外间距为 min(-20px, -10px)

本节三部分内容的完整示例:

See the Pen Margin collapsing by tcatche (@tcatche) on CodePen.

取消边距合并

这个很简单,破坏上述发生合并的前提条件即可,通常是通过创建块格式上下文解决:

如在 p 外面包裹一层容器,并触发该容器生成一个 BFC。那么两个 P 便不属于同一个 BFC,就不会发生 margin 合并了:

<div class="block">
<h2>相邻的兄弟元素会发生边距合并</h2>
<p style="margin-bottom: 40px;">下外边距不被合并...</p>
<div style="overflow: hidden"><p style="margin-top: 20px;">...上外边距不被合并。</p></div>
</div>

当然也可以使用 clear 属性解决,同时也会解决非浮动块的边框无法包括浮动元素的问题。

块格式上下文(Block formatting contexts)

首先普通流包含三种:块格式上下文(Block formatting contexts, BFC)、inline formatting contextRelative positioning

块格式上下文页面是普通流的一部分。一个块格式化上下文由以下之一创建:

  • 根元素
  • floats 值不是 none
  • 绝对定位元素即 positionabsolutefixed 的元素
  • display: inline-blocktable-cells(HTML表格单元格默认属性)、table-captions(HTML表格标题默认属性)、flexinline-flex
  • overflow 值不是 visible

在块格式上下文中,从容器块的顶部开始,垂直地布置内部块。两个兄弟块之间的垂直距离由 margin 属性决定,块格式化上下文内相邻块之间的垂直边距会发生边距合并。

在块格式上下文中,每个框的左外边缘挨着容器块的左边缘(如果是从右到左的格式,则从右边开始)。即使在浮动状态的情况下也是如此(尽管盒子的线框可能由于浮动而收缩),除非盒子建立了一个新的块格式化环境(在这种情况下盒子本身可能由于浮动而变得更窄)。

块格式上下文的特点

块格式化上下文的行与普通块类似,除了这几条:

块格式化上下文可防止边缘合并

相邻块框只有在相同的块格式上下文中才垂直边距合并。换句话说,如果相邻的框不属于相同的块格式上下文,则它们的边距不会崩溃。

<div class="block1">
<h2>块格式化上下文可防止边缘合并</h2>
<p class="p1">下外边距被合并...</p>
<p class="p1">...上外边距被合并。</p>
<div style="overflow:hidden"><p class="p1" >...上外边距不被合并。</p></div>
</div>
.p1 {
height: 20px;
border: 1px solid #f00;
margin: 20px;
background: green;
}

块格式化上下文可包含 float 块

如果一个float 块位于块格式化上下文内,则被其容纳,否则不被包括。

<div style="border: 1px solid #333">
<h2>块级父元素与其第一个和最后一个子元素发生边距合并</h2>
<div class="block">
<h3>不包含内部 float 块</h3>
<p class="p1" style="float: left;">
内部块1不在父div框中
</p>
</div>
<div class="block" style="overflow:hidden">
<h3>包含内部 float 块</h3>
<p class="p1" style="float: left;">
内部块2在父div框中
</p>
</div>
</div>

在上述示例中,内部第二个 block 创建了块格式化上下文,因此其能容纳内部的 float,而第一个 block 并不是,所以 float 在其边框之外。

块格式化上下文和 float 块不会合并

根据规范,块格式化上下文的边框不与同一块格式上下文中的浮动块边框相合并。如此,应用于浮动块旁边的块格式化上下文的负边距不起作用。小于其宽度(元素宽度+边框+边距+补白)的边距也不起作用。

<div class="block">
<h2>空内容的块元素会发生边距合并</h2>
<div class="block1" style="float: left;width:100px;padding: 10px">
<h3>float 块</h3>
</div>
<div class="block2" style="overflow:hidden;width:100px;margin-left: 20px">
<h3>块格式化上下文块</h3>
</div>
</div>

在这个例子中,如果要设置 block2 和 block1 的间距位 margin-left: 20px 并不起作用,实际上需要设置的值为 block1 的 width + border * 2 + padding * 2 + margin-left + 自身的margin-right + block2 的 margin-left ,当然这样很繁琐,可以简单设置 block 的 margin-right 为 block2 的 margin-left + block1 的原来的 margin-left。

.block1 {
margin-right: 20px;
}
// or
.block2 {
margin-left: 140px; // 100px + 10px*2 + 20px
}

常见的浮动布局就是利用这一特性实现的,这里不在说明。

本节三部分内容的完整示例:

See the Pen Block formatting contexts by tcatche (@tcatche) on CodePen.

参考