BFC的一些探究

最初的梦

了解BFC后,能够更深入的明白外边距合并原理
了解BFC后,能够更深入的明白浮动的行为
了解BFC后,知识就是你的,总不会吃亏对吧?哈哈

之前有两篇文章《行内元素的一些探索》《浮动的一些探索》,或多或少隐式的牵连到了块级格式化上下文

今天正式介绍BFC,以加深对CSS的理解。

-------------写在前面---------------
BFC是时候表演真正的技术了之前的内容是参考MDN的块级格式化上下文和自我思考的过程组成,今日重读感觉糟糕之极,于是便在此给出w3c规范中的定义以作补充。

W3C规范中是这样定义BFC的:

Floats, absolutely positioned elements, block containers (such as inline-blocks, table-cells, and table-captions) that are not block boxes, and block boxes with ‘overflow’ other than ‘visible’ (except when that value has been propagated to the viewport) establish new block formatting contexts for their contents.

这里需要注意的几个点block containersblock boxestheir contents

浮动、绝对定位元素,非块级盒子的块级容器(比如display属性值为inline-block,tale-cells和table-captions的元素),以及overflow属性设置为visible以外的块级盒子都为它们的内容创建了新的块级格式化上下文。

原文在这
block formatting context

彻底了解w3c规范BFC的定义后可以直接阅读最后一部分内容(bfc是时候表演技术)

BFC的生理构造

BFC外衣

块级格式化上下文(block formatting context)是页面CSS视觉渲染的一部分。它是用于决定盒子的布局及浮动相互影响范围的一个区域BFC之间不会相互影响。

小白反思

一共三句话。
第一句话忽略不计。

第二句话很关键。不过先反问下,这句话里的盒子指的是谁,也就是说决定谁的浮动和布局?

这个问题的答案其实很简单,这里的盒子指代的是产生BFC元素块级子元素

反思:什么条件产生BFC,决定谁的布局?
解答:BFC的产生见下文。块级格式化上下文(BFC)从字面上理解应该能推测出盒子指代的是块级子元素

第三句话也很重要。BFC之间不会相互影响,那么BFC和其它的元素会发生影响,BFC中的元素和其它的元素会发生影响嘛?

答案是肯定的,BFC和其它元素会发生影响,但BFC中的元素不会受其它元素影响。

反思:这里的其它元素指的是什么元素?
解答:处在同一个包含块中同级别(互为兄弟关系)的元素。

BFC的心

官方外衣总是华丽,值得琢磨,尽管仔细思考了也必然有所获。但,作为程序员,怎能不去了解她的心。

块级格式化上下文包括了创建该上下文的元素的所有子元素,但不包括创建了新的块格式化上下文的子元素。

小白反思

如果世界的运行都有自我运行的一套机制,那么BFC的世界必然有自己的一套规则。世界的运行总避免不了能力超凡者打破规矩、突破限制形成另一个世界并不再受之前的约束。这里的能力超凡者就可以理解为创建了新的块级格式化上下文的元素。那么如果获得创造新世界的能力呢?

BFC,你是怎么来到这个世界的?

世界从来都不是平等的,或许就像海贼王里的天龙人,高高在上。BFC有些元素与生俱来,有些就靠开发者来赋予。

  1. 根元素或其它包含它的元素

  2. 浮动和定位元素(设置了float不为none或position不为relative和static)

  3. overflow不为visible的元素

  4. display设为inline-block、table-cell和table-caption

新的世界,总会形成一套自己的规矩。

BFC的游戏规则

  1. 在BFC中,盒子从顶端开始垂直地一个接一个排列,两个盒子之间的垂直间隙是由他们的margin值决定

  2. 同一个BFC中,两个相邻块级盒子的垂直外边距会产生重叠

  3. 在BFC中,每一个盒子的左边缘都会触碰到容器到左边缘

  4. 计算BFC元素高度时,浮动元素也参与计算

BFC是时候表演真正的技术了

文字环绕

《BFC的一些探究》

这是一种常见的现象。有些时候,实际需要的效果是下面这样的。

《BFC的一些探究》

为了直观的看到BFC的作用,给段落p添加个背景色。

《BFC的一些探究》
清晰看见p的行框有一部分是在绿色透明块之下的,现在赋予p元素BFC的话,那么浮动的Div和p就相当于超强者(各自形成自己的BFC),根据BFC之间互不影响这一特性。效果就是下面这样的。

《BFC的一些探究》

外边距合并

当时在回答div外边距的问题时,总结出了合并的一条规则:margin必须相邻。但当时只探究了margin合并的条件,却未曾探究条件的产生(什么情况才算相邻)。现在从BFC的角度重聊这个话题。

margin相邻的前提条件

  1. 必须处于文档正常流块级盒子,并且处于同一个BFC

  2. 没有线盒,没有清除区域(clearance),没有padding和border

  3. margin都是垂直方向上的

针对第一点,进行一个简单的说明。

《BFC的一些探究》
这是常见的外边距合并。触发合并的因素符合相邻的前提条件。
代码如下

<style type="text/css">
        body{margin:0;padding:0;}
        .parent{width:400px;height:400px;background-color:#af3;margin:20px 0;}
        .child{width:200px;height:200px;margin:30px 0;background-color:#f3a;}
    </style>
</head>
<body>
    <div class="parent">
        <div class="child"></div>
    </div>
</body>

一般情况阻止外边距重叠会给父元素Div.parent设置overflow:hidden。

《BFC的一些探究》

那么如果给Div.child设置overflow:hidden呢?

.child{width:200px;height:200px;margin:30px 0;background:#f3a;overflow:hidden;}

《BFC的一些探究》

怎么会这样?Div.child不是产生了新的BFC吗?那么Div.parentDiv.child就不在同一BFC中了。那么不是违背了margin相邻的前提条件中的第一条了吗?为什么还发生重叠了?

反思:重叠既然发生了,说明一个问题,那就是margin相邻的条件全部满足了。这说明Div.parentDiv.child处在同一个BFC,overflow:hidden设置给父元素时确实发生作用并产生了新的BFC,并阻止重叠。
overflow:hidden设置给子元素时也确实作用并产生新BFC,但却没阻止重叠。

说到这,需要考虑的问题就是BFC概念了。
BFC的心这一节里有提BFC的内部组成。这里再次引用:

块级格式化上下文包括了创建该上下文的元素的所有子元素,但不包括创建了新的块格式化上下文的子元素。

一句话概括:BFC是为其内容而生的。

BFC不包括产生BFC的元素,也就是不包括生产者!

overflow:hidden设置给父元素时,阻断了重叠发生。这说明Div.parentDiv.child不处在同一个BFC中。child处在parent产生的BFC中,而parent处在最开始的BFC中。所以重叠不发生。

overflow:hidden设置给子元素时,重叠发生。这说明Div.parentDiv.child处在同一个BFC中。child产生的BFC并不会把child自己也纳入进去,而是作用于child的子元素。因此parent和child依然处于同一个BFC中,所以重叠发生。

小白提问
考虑下Div.parent和Div.child单独设置display:inline-block时,会发生什么现象?

答案或许是这样的,和overflow一样呗。
实践才出真理。
无论是给父元素还是子元素设置display:inline-block,重叠都不会发生。为什么呢?不都是产生了BFC吗?

小白揭秘
margin相邻前提条件的第一条里是要求必须是块级盒子,而在w3c规范中规定,块级盒子的display属性必须是以下三种之一:’block’, ‘list-item’, 和 ‘table’。display:inline-block不在其列,因而无论给谁设置display都不满足条件。所以重叠不发生。

clearance对外边距的影响

下面代码中的margin-top方向为什么没有产生下移的视觉效果,而margin-left方向居然产生了左移的效果。

<style type="text/css">
    /*body {
        margin: 0;
        padding: 0;
    }*/
    section {
        background-color: #af3;
    }
    .float {
        float: left;
        border: 5px solid #fe3;
        width: 200px;
    }
    .child {
        clear: both;
        margin: 1000px 200px;
        border: 5px solid;
        width: 200px;
        height: 200px;
        background-color: #f3a;
    }
</style>
</head>
<body>
    <section>
        <div class="float"></div>
        <div class="child"></div>
    </section>
</body>

《BFC的一些探究》

这个问题困惑了我很久。浅谈个人对该问题的理解:
在阅读前,最好深入margin属性。如果不愿意看的话,那就直接看下面这段话吧。

在 margin 中 top、right、bottom、left 的参考线并不一致为一类,而是分为了两类参考线。top 和 left 的参考线属于一类,right 和bottom 的参考线属于另一类。top 以 containing blockcontent 上边或者垂直上方相连元素 margin 的下边为参考线垂直向下位移;left 以 containing blockcontent 左边或者水平左方相连元素 margin 的右边为参考线水平向右位移。right 以元素本身的 border 右边为参考线水平向右位移;bottom 以元素本身的border 下边为参考线垂直向下位移。

小白分析
这段话首先将trbl分成了两类。
其中top和left的参考线又分为两种。

  1. container block content top(left)
    top和left分别对应父元素content top edgecontent left edge

  2. sibling margin-bottom (margin-right)
    top和left分别对应相邻元素margin-bottommargin-right(这里当然会发生margin collapse)

bottom和right是以元素自身border-bottom(border-right)为边界。

问题剖析
1.margin-left发生了水平偏移,且child在水平方向并没有相邻元素,这说明其参考是父元素(section)的content left edge
2.margin-top没发生偏移,这至少说明其参考线不是父元素(section)的content top edge。然而在垂直方向上,child是拥有一个相邻的东西(clearance),那么很明显就是clearance捣鬼。如果clearance被解析成盒模型了,那么垂直方向肯定能产生位移,如果解析成其他一些东西了,那么没有位移就很正常了。 (这一点也模糊,希望有人指点)

w3c clearance

两列布局,我来讲点不一样的

两列布局的要求:
一栏宽度固定一栏宽度随浏览器发生改变

以下最常见的代码,没有任何问题。

<style type="text/css">
    body,aside,main {
        margin: 0;
        padding: 0;
    }
    aside {
        float: left;
        width: 200px;
        height: 50px;
        background-color: #3fa;
    }
    main {
        height: 50px;
        text-align: center;
        background-color: #af3;
    }
</style>
</head>
<body>
    <aside>This is aside</aside>
    <main>This is main content</main>
</body>

《BFC的一些探究》

换个思路实现两列布局,各位看官看好喽。

<style type="text/css">
    body,aside,main {
        margin: 0;
        padding: 0;
    }
    aside {
        float: right;
        width: 200px;
        height: 50px;
        background-color: #3fa;
    }
    main {
        float: right;
        margin-left: -200px;
        width: 100%;
        height: 50px;
        text-align: center;
        background-color: #af3;
    }
</style>
</head>
<body>
    <main>This is main content</main>
    <aside>This is aside</aside>
</body>

《BFC的一些探究》

这里做了三处修改

  1. 将main与aside的位置交换

  2. 为main添加右浮动,改变aside浮动方向,并显式设置了width:100%

  3. 为main添加了margin-left:-200px;

这样看上去是没有任何一点问题的。

如果向main里添加更多的文字会发生什么?

《BFC的一些探究》

那么第一种两列布局会不会发生上面这种情况呢?

《BFC的一些探究》

你真的懂BFC了吗?

小白解析

  1. 第一种布局之所以不会发生重叠,是因为它们在同一个BFC中

  2. 第二种布局之所以会重叠,是因为它们在不同BFC中

圣杯、双飞翼,我也来玩

问题产生

<style type="text/css">
    body,.left,.right,main {
        margin: 0;
        padding: 0;
        text-align: center;
    }
    main {
        float: left;
        width: 100%;
        height: 50px;
        background-color: #3fa;
    }
    .left {
        float: left;
        margin-left: -100%;
        width: 250px;
        height: 50px;
        background-color: #af3;
        opacity: 0.4;
    }
    .right {
        float: left;
        margin-left: -100px;
        width: 100px;
        height: 50px;
        background-color: #f3a;
        opacity: 0.4;
    }
</style>
</head>
<body>
    <div>
        <main>I am boss</main>
        <aside class="left">Left</aside>
        <aside class="right">right</aside>
    </div>
</body>

基本描述:此时给中间绿色块div填写内容会出现重叠现象,原理同上述两列布局
《BFC的一些探究》

为了解决这个问题,出现两种思路(或许双飞翼是圣杯的优化方案)

圣杯好像很好吃的样子~

<style type="text/css">
    body,.left,.right,main {
        margin: 0;
        padding: 0;
        text-align: center;
    }
    div {
        padding: 0 100px 0 250px;
    }
    main {
        float: left;
        width: 100%;
        height: 50px;
        background-color: #3fa;
    }
    .left {
        position: relative;
        left: -250px;
        float: left;
        margin-left: -100%;
        width: 250px;
        height: 50px;
        background-color: #af3;
        opacity: 0.4;
    }
    .right {
        position: relative;
        right: -100px;
        float: left;
        margin-left: -100px;
        width: 100px;
        height: 50px;
        background-color: #f3a;
        opacity: 0.4;
    }
</style>
</head>
<body>
    <div>
        <main>I am boss</main>
        <aside class="left">Left</aside>
        <aside class="right">right</aside>
    </div>
</body>

《BFC的一些探究》

以上就是圣杯布局。分析就不写了。直接看双飞翼布局吧,非常精妙的布局。

<style type="text/css">
    body,.left,.right,main {
        margin: 0;
        padding: 0;
        text-align: center;
    }
    .wrap-main {
        float: left;
        width: 100%;
    }
    main {
        margin: 0 100px 0 250px;
        height: 50px;
        background-color: #3fa;
    }
    .left {
        float: left;
        margin-left: -100%;
        width: 250px;
        height: 50px;
        background-color: #af3;
        opacity: 0.4;
    }
    .right {
        float: left;
        margin-left: -100px;
        width: 100px;
        height: 50px;
        background-color: #f3a;
        opacity: 0.4;
    }
</style>
</head>
<body>
    <div>
        <div class="wrap-main">
            <main>I am bossI am bossI am bossI am bossI am bossI am bossI am bossI am bossI am bossI am bossI am bossI am bossI am bossI am bossI am bossI am bossI am bossI am bossI am bossI am bossI am bossI am bossI am bossI am bossI am bossI am bossI am boss</main>
        </div>
        <aside class="left">Left</aside>
        <aside class="right">right</aside>
    </div>
</body>

《BFC的一些探究》

小白分析
首先在不考虑使用position:relative的情况下,要达到圣杯布局的效果,那只能通过margin来作用。先贴张问题图方便阅读

《BFC的一些探究》

那么直接给main(绿色的div)添加margin: 0 100px 0 250px;不就行了吗?

《BFC的一些探究》
《BFC的一些探究》

由上述两张图可知道margin属性只有左边的发生作用了,而右边的未发生作用。这是因为属性过分受限(overconstrained),将margin-right强制设为auto了。(调试器看不出来的)

因此该思路不可行。

于是便思索通过水平格式化这一idea来实现。

  1. 给main套一层div.wrap-main

  2. 将main的部分属性(float、width)移到div.wrap-main,并为main添加margin: 0 100px 0 250px;

至此,双飞翼布局已经实现。

涉及知识点总结

  1. 水平格式化

  2. 属性过分受限(overconstrained)

  3. margin属性深入了解

  4. bfc的深入认识

  5. float的深入了解

最后一点小收获

《BFC的一些探究》
这张图应该很有印象吧,前面有哟。

为什么这里有个空白?aside.right在哪里呢?

尽管main的margin-right水平受限被设置为auto,但在计算和渲染时,100px仍然会发生作用。所以aside.left的margin-left:-100%不是靠着最左边而是间隔了100px。受其影响,aside.right的margin-left:-100px就把自己藏在了最左边(下图的位置)。
以上纯属个人理解
《BFC的一些探究》

    原文作者:Blcover
    原文地址: https://segmentfault.com/a/1190000004399966
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞