BFC和自适应两栏布局、清除浮动

最初我们写两栏布局时,使用的是padding,和float:left结合实现两栏布局,这种方式确实实现了两栏布局,但是我们在写的时候,必须明确其中一栏的宽度,然后再使另一栏实现自适应的宽度,代码如下:

.aside{
    background: #aa1;
    width: 150px;
    height: 400px;
    float: left;
}
.main{
    background: #777;
    margin-left: 170px;
    height: 500px;
}

HTML如下:

<div class="aside"></div>
<div class="main"></div>

还有另一种方式,我们不需要知道左边栏的宽度,就可以是右边的栏实现自适应布局,这就是我今天要分享的重点

什么是BFC ?

w3c 上是这样讲的:

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.

根据w3c的意思,一下这几种情况会建立一个新的会计格式化上下文,大概有这几种

  • float 属性不是none;
  • position属性时absolute或者是fixed
  • block container 例如: inline-block,table-cell,table-caption等
  • overflow属性不是visible的
  • 根元素

触发BFC的元素的特点?

w3c上的描述:
https://www.w3.org/TR/CSS2/visuren.html#block-formatting

大致总结为几点:

  • 内部的box会一个一个的垂直排布,盒子之间的距离会由margin来决定
  • 在同一个块级格式化上下文中两个相邻的block的margin会发生重叠
  • 每个box与包含块左侧相邻
  • BFC的区域不会与属性为float 的box重叠
  • BFC是页面上一个隔离的独立容器,内部的子元素是与外界无关的
  • 计算BFC 的高度时,浮动元素也参与在内

个人认为,触发一个元素成为BFC可以拥有一下几个比较重要的特点:

  1. 块级格式化上下文不会与属性为float的元素发生重叠
    那些年我们一起清除的浮动,这里面有提到为什么不会发生重叠:

    根据规定,一个块级格式化上下文的边框不能和它里面的元素的外边距重叠。这就意味着浏览器将会给块级格式化上下文创建隐式的外边距来阻止它和浮动元素的外边距叠加。由于这个原因,当给一个挨着浮动的块级格式化上下文添加负的外边距时将会不起作用

至于是不是创建了隐式的外边距,我没有查到,但是所提到的效果确实如此

  1. 块级格式化上下文会计算浮动的高度,使浮动自动闭合
  2. 块级格式化上下文会防止内层元素的margin影响外层元素的布局
  3. 触发BFC相当于在页面上创建了一个隔离的独立的容器,其内部的子元素不会与外界发生关系
    实际上以上那两特点都来自于块级格式化上下文这一个基本的特点

我们逐一来介绍上面的这几个特点:

###BFC的区域不会与float box发生重叠 ###

如果我们这样写:

<style type="text/css">
    .aside{
        width: 25%;
        height: 200px;
        float: left;
        background-color: #832;
    }
    .main{
        height: 400px;
        background-color: #97a;
    }
</style>
<body>
    <div class="aside"></div>
    <div class="main"></div>
</body>

得到如下效果图:
no BFC

但是当我们将main变成BFC时,会发生什么呢?我们将main加上一个样式

overflow: hidden;

得到如下效果图:
BFC
这就是由于BFC区域并不会与float box重叠,所以他会根据float box的宽度来调整自己的宽度,他的宽度会自动缩减。

另外,对于上述两栏布局,在我们想要设置右边栏的margin的时候,会发现他是没有效果的,代码如下:

    <style type="text/css">
    .aside{
        float: left;
        height: 200px;
        width: 20%;
        background: #901;
    }
    .main{
        height: 400px;
        background-color: #97a;
        overflow: hidden;
        margin-left: 20px;
    }
</style>

HTML如下:

<div class="aside"></div>
<div class="main"></div>

效果如下:
margin设置

从图中可以看出来, 实际上是有margin出现的,但是margin与左边的float框发生了重叠,所以,并没有起到分离的作用。由此,我们可以得出结论,在对BFC设置相对于float元素的margin的时候,是会与float元素发生重叠的,但是假如设置的是float元素的右边框呢?
这时候神奇的事情就发生了~~~~margin出现了,这个说明,左右两个触发BFC的块级框的地位是不一样的
可以这样理解,属性为float的元素是脱离文档流的,而触发BFC的元素是在文档流中的,虽然触发BFC的元素不想和属性为float的元素发生任何关系,但是它也改变不了float不在文档流中的事实,所以设置的margin会出现问题。

计算高度时,浮动元素也参与在内

这个属性对于清除浮动有很大的意义~~~
平常写的浮动元素与包含块的关系:代码如下

<style type="text/css">
    .parent{
        border:solid 2px #421;
    }
    .child{
        float: left;
        width: 100px;
        height: 100px;
        background-color: #97a;
        border: solid 2px #099;
    }
</style>
<body>
    <div class="parent">
        <div class="child"></div>
        <div class="child"></div>
    </div>
</body>

得到的效果图如下:
float_noBFC
实际上这时候外层容器是没有高度的,只有边框,之前我想要将外层容器和内层元素的高度设置一致时,通常我会清除浮动
对于清除浮动,那些年我们一起清除的浮动,这里面有提到很详细的解决方法,我认为他实际上只用了两种方式:

  1. 使元素自身触发BFC来闭合浮动
  2. 使用另一个元素清除浮动
    我们先简单的看几个清除浮动的方式:
    使用其余已存在元素清除浮动:
    代码如下:

    .par{
        border: solid 2px #12a ;
    }
    .child{
        float: left;
        height: 100px;
        width: 100px;
        background-color: #97a;
        border: solid 2px #128;
    }
    .footer{
        height: 20px;
        clear: both;
        background: #f29;
    }
    

HTML如下:

<div class="par">
    <div class="child"></div>
    <div class="child"></div>
</div>
<div class="footer"></div>   

这时候,我们使用par 元素下面的一个div元素清除的浮动,这是实现的结果:
footer
我们发现会有一些问题,因为其最后的元素虽然都正常布局了,但是中间的class名为par的元素并没有高度(为什么还有待考察)
我们看另一种,after伪元素清除浮动
css代码如下:

.parent{
    border:solid 2px #421;
}
.child{
    float: left;
    width: 100px;
    height: 100px;
    background-color: #97a;
    border: solid 2px #099;
}
.parent:after{
    content: ".";
    visibility: hidden;
    clear: both;
    height: 0px;
    display: block;
}

HTML如下:

<div class="parent">
    <div class="child"></div>
    <div class="child"></div>
</div>

这是会产生如下效果图:
clear

外层容器的高度会包含内层元素的高度,闭合浮动相当于在外包元素后面添加了一个块级元素,然后这个块级元素会放在浮动元素的后面,撑起了内部的高度,从而计算了内部浮动元素的高度。

同样,如果我们将外层容器变做一个BFC容器,由于BFC在计算高度时,也会将浮动元素计算在内,所以如果将外层容器加上一个样式:

overflow: hidden;

也会产生上述效果。
不只是使用这一个属性,使用任何一种触发元素成为BFC的属性都会实现上述效果。

BFC的另外一个特点:

不同的块级格式化上下文margin不会重叠,同一个格式化上下文中margin会发生重叠

在一般的块级元素垂直排放的时候,会发生margin的重叠,这种margin的重叠不只是发生在同级的元素中,就是一个元素的子元素和兄弟元素的margin也会发生重叠,
代码如下:

    <style type="text/css">
.box {
    background: #89f;
    margin: 10px auto;
}
.child {
    height: 50px;
    width: 50px;
    background-color: #97a;
    border: solid 2px #128;
    margin: 10px auto; 
}
</style>

HTML如下:

<div class="box">
    <div class="child"></div>
    <div class="child"></div>
</div>
<div class="box">
    <div class="child"></div>
    <div class="child"></div>
</div>

我们会发现,在这种情况下,子元素的margin会和外层父元素的margin以及父元素的兄弟元素相重叠(前提是,我们没有给父元素设置border属性(为什么)
但是,当我们触发父元素成为BFC之后,会发现,子元素的margin被封装起来了,并不会越界,因为一个BFC区域是一个独立的渲染层,所以内部渲染并不会影响其他的元素,其他的元素也不会影响内部元素的渲染。

以上是我对BFC的粗浅理解,还有些地方没搞明白,等到搞明白的时候在补上。

参考资料:
【1】CSS深入理解流体特性和BFC特性下多栏自适应布局
【2】前端精选文摘:BFC 神奇背后的原理(转)
【3】那些年我们一起清除过的浮动
【4】https://www.w3.org/TR/CSS2/box.html#collapsing-margins