8.7 相邻父子的margin折叠与避免
8.7.1 规则详情
(1)规则描述:同一BFC内,父元素的margin-top与第1个子元素的margin-top进行折叠。同理,父元素与最后1个子元素的margin-bottom会发生折叠。后续主要针对margin-top方向说明,不再赘述margin-bottom方向。
(2)详情描述:margin-top方向折叠时要求父元素没有设置padding-top与border-top。
(3)折叠后显示效果:父子元素的内容区的顶部完全一致。
折叠后显示效果包括如下3种场景。示例代码
<body>
<div class="div1">div1</div>
<div class="div2">
<p class="p1">p1 小步教程 xiaobuteach.com</p>
</div>
<div class="div3">div3</div>
</body>
场景1:父子元素margin-top相等,完全重合。
场景2:父元素大于子元素margin-top时,正常折叠。
场景3:父元素小于子元素margin-top时,子元素margin-top穿透父元素。
上述三种场景的共同点:父元素div2与p1的内容区的顶部位置重合。
8.7.2 不合理的折叠示例
折叠是否合理取决于需求(也就是期望),这里列举不符合期望的场景。
初始页面代码如下。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>margin折叠 | 小步教程</title>
<style>
body {
margin: 0;
}
.div1 {
border-bottom: 1px solid #666;
}
.div2 {
/* margin-top: 20px; */
background-color: #0aaa76;
width: 250px;
}
.div2-1 {
/* margin-top: 30px; */
background-color: #4aa6fc;
height: 30px;
margin-left: 20px;
}
.div2-2 {
/* margin-top: 20px; */
background-color: #ed7d31;
height: 30px;
margin-left: 20px;
}
.div3 {
border-top: 1px solid #666;
}
</style>
</head>
<body>
<div class="div1">div1</div>
<div class="div2">
<div class="div2-1">div2-1 小步教程</div>
<div class="div2-2">div2-2 xiaobuteach.com</div>
</div>
<div class="div3">div3</div>
</body>
</html>
运行效果
新需求。div2、div2-1、div2-2设置margin-top值:20px、30px、20px,期望效果如下。
添加相应margin-top代码(取消注释),实际效果与期望不符。
开发者工具查看div2与div2-1两个盒子的页面显示。
子元素div2-1的margin-top区域超出父元素div2的margin-top区域。margin折叠的这种特殊场景称为margin穿透。折叠后实际效果:div2距div1的间距变成30px,div2-1与div2之间没有margin-top效果,内容区顶部重合。div1与div2-2不相邻不会折叠。
8.7.3 父子margin折叠的本质
父子margin-top折叠之BFC示意图
条件1:父div与子div在同一个BFC,图示BFC-X。BFC-X不一定是html元素新建BFC-root,其它特殊元素新建的BFC也适用,例如BFC-2、BFC-2-1等。
条件2:两元素的margin垂直方向相邻。
父子margin-top折叠之盒子示意图
(1)如果父元素没有设置padding-top与border-top(或设置为0),父子元素的margin-top区域就会相邻,发生折叠。
(2)如果父元素设置padding-top或border-top,父子margin-top不再相邻,不会折叠。
(3)如果子元素设置padding-top或border-top,父子margin-top仍然相邻,发生折叠。
8.7.4 新建BFC避免margin折叠
(1)新建BFC解决方案
新建BFC打破margin折叠特定结构。桔色虚线表示在父div内部新建BFC对象BFC-Y,则两个元素不在同一BFC,更严格的说法是,两个margin区域不在同一BFC。所以不重叠。
示例代码
.div2{
overflow: hidden;/*新添加代码,创建新的BFC*/
}
(2)新建BFC解决方案的本质
常见错误理解ConceptError010:胡乱新建BFC就不存在margin折叠。
每个BFC内部都不存在特定结构才不存在margin折叠。如果新建的BFC内部仍存在特定结构,仍会折叠;如果新建BFC没有避免外部BFC的特定结构,仍会折叠。新旧BFC(图示BFC-X与BFC-Y)内都不存在margin折叠特定结构,则完全不存在折叠。
(3)专门新建BFC的属性值flow-root
创建BFC的方式有很多,常使用两种方式:设置display: flow-root或overflow: hidden。display: flow-root是专门的、明确的新建BFC的CSS属性。overflow: hidden是将元素设置成可滚动区域并隐藏滚动条的CSS属性,新建BFC属于它的“顺带”功能。
如果只是新建BFC推荐使用flow-root方法,CSS3才推出flow-root属性所以早期大量使用overflow;如果需要隐藏滚动条设置并“顺带”新建BFC则使用overflow。
8.7.5 新建BFC无法避免margin折叠
新建BFC元素的margin参与外部BFC的margin折叠判定。胡乱新建BFC没有打破原有BFC内的margin折叠特定结构,则无法避免折叠。
示例场景。在子元素新建BFC无法解决margin穿透。示例代码。
.div2-1 {
overflow: hidden;
}
运行效果,仍然存在margin穿透。
目前BFC示意图
新建BFC对象在相应元素的内部,也就是BFC包含border、padding与内容区,但BFC并不包含元素的margin区。而新建BFC元素的margin参与外部BFC的margin折叠判定。
含margin-top区域的BFC示意图
父div的margin-top与子div的margin-top仍然在同一BFC(根BFC-X),并且相邻,所以仍然折叠。所以上述结构仍然存在特定结构。综上所述,margin折叠发生条件的本质简单表述为:同一BFC,margin区域相邻。从盒子属于哪个BFC对象来看,严格来说,创建BFC的元素,一部分在所属外部BFC,即margin区域,一部分在自己内部创建的BFC,即border及内部区域。
margin折叠发生条件的精准定义:两个相邻垂直margin在同一个BFC。通过这个精准定义,能够轻松理解各种场景下padding/border对margin折叠的影响,理解区域所属元素关系可能是父子关系、兄弟关系、同一元素的各种场景。
8.7.6 理解CSS属性新建BFC
“8.4.3 BFC规范规则2:特殊元素新建BFC对象”列举大量的CSS属性自动创建BFC。BFC的本质即流式布局,当一个CSS属性改变元素的定位方案、内部显示类型display-inside、滚条设置、等情况,该元素就具有相应特殊性,但同时希望该元素的内部元素仍然是流式布局,所以CSS定义这项规则,在它内部新建BFC。
围绕margin折叠,需要理解流式布局、display模型、FC、BFC、相关规范规则。开发者工具不直接提供BFC的调试、无法直接看到BFC对象,可以通过各种间接方式进行验证。
到这里能够进一步理解CSS是一门模型化与规范规则化语言。
8.7.7 非新建BFC方法参考
请根据需求场景选择性使用。
方法1:合理运用折叠,合理设置margin值,实现目标效果。
方法2:父元素设置padding-top或border-top,则父子margin-top不相邻。
方法3:将第1个子元素margin-top改成设置padding-top或父元素padding-top。