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相等,完全重合。

img


场景2:父元素大于子元素margin-top时,正常折叠。

img


场景3:父元素小于子元素margin-top时,子元素margin-top穿透父元素。

img

上述三种场景的共同点:父元素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>

运行效果

img


新需求。div2、div2-1、div2-2设置margin-top值:20px、30px、20px,期望效果如下。

img


添加相应margin-top代码(取消注释),实际效果与期望不符。

img


开发者工具查看div2与div2-1两个盒子的页面显示。

img

子元素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示意图

img

条件1:父div与子div在同一个BFC,图示BFC-X。BFC-X不一定是html元素新建BFC-root,其它特殊元素新建的BFC也适用,例如BFC-2、BFC-2-1等。

条件2:两元素的margin垂直方向相邻。

父子margin-top折叠之盒子示意图

img

(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解决方案

img

新建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示意图

img

新建BFC对象在相应元素的内部,也就是BFC包含border、padding与内容区,但BFC并不包含元素的margin区。而新建BFC元素的margin参与外部BFC的margin折叠判定。

含margin-top区域的BFC示意图

img

父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。