> For the complete documentation index, see [llms.txt](https://lizh.gitbook.io/knowledge/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://lizh.gitbook.io/knowledge/css-xi-lie/03-you-fu-dong-ta-xian-yin-fa-de-kuai-ji-ge-shi-shang-xia-wen-si-kao.md).

# 由浮动塌陷引发的块级格式上下文思考

**浮动最初的定义：实现文本环绕图片的效果**。除此之外，没有其他方法可实现。

![image-20200730213436644](https://my-files-1259410276.cos.ap-chengdu.myqcloud.com/md_images/image-20200730213436644.png)

[查看文本环绕DEMO](https://1927344728.github.io/demo-lizh/html/02_float.html?type=1)

## 什么是浮动?

CSS 中的浮动是用 `float` 属性来定义：`float: left | right | none | inline-start | inline-end`

`float` 属性值不为 `none`，会对 `display` 属性的值产生影响：

* `block、inline-block、inline、table-*` -> `block`
* `inline-table、table` -> `table`
* `flex、inline-flex` -> 值不变，但是 `float` 对这样的元素不起作用
* 其他值不变

当一个元素浮动之后，它会被移出正常的文档流，然后向左或者向右平移，一直平移直到碰到了所处的容器的边框，或者碰到**另外一个浮动的元素**。浮动元素不属于文档中的普通流，属于浮动布局。顾名思义，浮动就是漂浮于普通流之上，像浮云一样，但是只能左右浮动。

> CSS 有三种基本的定位机制：普通流、浮动（float: left|right）和绝对定位（position: absoulte）。

**注意：** 浮动元素只在水平方向浮动，也就是说，只能左右移动而不能上下移动；并且它会尽量向左或向右移动，直到它的外边缘碰到包含框或另一个浮动框的边框为止，也就是说，它只能居左或居右，而无法居中。

![image-20200730224651452](https://my-files-1259410276.cos.ap-chengdu.myqcloud.com/md_images/image-20200730224651452.png)

```html
<section class="box_02">
    <div class="item_02">
        <div class="container_float"><img src="./images/pins_3333913039.jpg"></div>
        <div class="bg"></div>
    </div>
    <div class="item_02">
        <img class="img_float" src="./images/pins_3333913039.jpg">
        <div class="text">读书，去别人的灵魂里偷窥。旅行，去陌生的环境里去感悟。电影，去荧屏里感受别人的生活历程。冥想，去自己内心的秘境里探寻。<br />读书，去别人的灵魂里偷窥。旅行，去陌生的环境里去感悟。电影，去荧屏里感受别人的生活历程。冥想，去自己内心的秘境里探寻。</div>
    </div>
</section>
```

```css
<style>
.box_02 img {
    width: 100%;
}
.box_02 > .item_02 {
    padding: 20px;
    border: 1px solid #0aa;
}
.box_02 > .item_02 .container_float,
.box_02 > .item_02 .img_float {
    float: left;
    width: 100px;
    margin-left: 20px;
    margin-right: 20px;
}
.box_02 > .item_02 .bg {
    height: 50px;
    margin-top: 5px;
    background: #0aa;
}
</style>
```

[查看 float DEMO](https://1927344728.github.io/demo-lizh/html/02_float.html?type=2)

**浮动会造成哪些影响呢？**

浮动元素会对其相邻元素和父元素造成不同影响：

* **对相邻元素（紧邻浮动元素后面的元素）：** 如果相邻元素是普通流的块级元素，则**不会影响块级元素的布局**（如上图的背景色模块——块级元素与浮动元素处于同一行，但其宽度占据了父元素的整个内容区域，浮动元素只是悬浮在块元素上方），**但会影响块级元素包含的内联元素的排列**（内联元素环绕浮动元素）；

  如果相邻元素是普通流的内联元素，则会影响内联元素的布局和其包含的内联元素的排列（如上图的文本模块——内联元素的位置是紧跟浮动元素，而不是从父元素的最左侧开始，且文本会尽可能围绕浮动元素）。

  **注意：** 浮动元素之前的元素将不会受到影响。
* **对父元素：** 父元素在获取高度计算时，会忽略浮动元素，即不计算浮动元素高度。**这就是浮动塌陷，也称高度塌陷现象。**

## 什么是块级格式化上下文?

**块格式化上下文（Block Formatting Context，BFC）** 是 Web 页面的可视 CSS 渲染的一部分，是块盒子的布局过程发生的区域，也是浮动元素与其他元素交互的区域。一个元素创建了 BFC 后，它就拥有了一个独立的环境，该环境内的子元素不会影响外面元素的布局，同样，外面的元素也不会影响其子元素的布局。

> 块级格式化上下文的特性与[层叠上下文](https://lizh.gitbook.io/knowledge/knowledges/css-tan-suo-xi-lie/css-tan-suo-xi-lie-zhi-css-ceng-die#shen-me-ceng-die-shang-xia-wen)相似，都是指独立的空间，不过，块级格式化上下文是 x、y 的独立空间，而层叠上下文是 z 轴上的独立空间。

### BFC 有哪些特性？

块格式化上下文对浮动定位（float）、清除浮动（clear）、外边距折叠（Margin collapsing）都很重要。浮动定位和清除浮动时只会应用于同一个 BCF 内的元素。**外边距折叠也只会发生在属于同一 BFC 的块级元素之间。**

BFC 的三个特性：

* **BFC 会阻止外边距折叠（**[**CSS探索系列之Margin**](https://lizh.gitbook.io/knowledge/knowledges/css-tan-suo-xi-lie/css-tan-suo-xi-lie-zhi-margin)**）**

  当两个相邻的块框在同一个块级格式化上下文中时，它们之间垂直方向的外边距可能会发生叠加。换句话说，如果这两个相邻的块框不属于同一个块级格式化上下文，那么它们的外边距就不会叠加。
* **BFC 不会重叠浮动元素**

  BFC 内的浮动不会影响其它 BFC 中元素的布局，而 `clear` 属性只能清除同一 BFC 中在它前面的元素的浮动。
* **BFC 通常可以包含浮动**，即计算 BFC 的高度时，浮动元素也参与计算

  独立的块级上下文可以包裹浮动流，全部浮动子元素也不会引起容器高度塌陷，也就是说父元素会把浮动元素的高度也计算在内，所以不用清除浮动来撑起高度。同时 BFC 仍然属于文档中的普通流。

  也就是说，**如果父元素创建了 BFC，就可以闭合浮动，避免浮动塌陷现象**

### 如何为元素创建 BFC？

一般来说，是通过 `display` 属性来创建 BFC ，具体如下：

* **根元素**
* **浮动元素：** `float` 属性值不为 `none`；
* **绝对定位元素：** `position` 属性值不为 `static|relative`；
* **行内块元素：** `display` 属性值为 `inline-block`；
* 元素的 `overflow` 属性值不为 `visible` ；
* 表格单元格：`display: table-cell`；
* 表格标题：`display: table-caption`
* 匿名表格单元格元素：`display: table | inline-table | table-row | table-row-group | table-header-group | table-footer-group`
* 元素的 `display` 属性值为 `flow-root`。**这是一个新的属性值，可以创建无副作用的 BFC，但 Safari 不支持**。
* 弹性元素：`display: flex | inline-flex` 元素的直接子元素
* 网格元素：`display: grid | inline-grid` 元素的直接子元素
* `contain: layout | content | paint`
* 多列容器：`column-count` 或 `column-width` 不为 `auto`、`column-count: 1`
* `column-span: all`。即使该元素没有包裹在一个多列容器中（标准变更，Chrome bug）。

> 注意：`display: table` 本身并不会创建 BFC，但是它会产生匿名盒子，而匿名盒子中的 `display: table-cell` 可以创建新的BFC，换句话说，触发块级格式化上下文的是匿名框，而不是 `display: table`。所以通过 `display: table` 和 `display: table-cell` 创建的 BFC 效果是不一样的。

## 什么是 hasLayout?

`hasLayout` 是 IE 浏览器私有的概念，可以简单看作是 IE 5.5/6/7 中的 BFC！

IE 浏览器使用 `Layout` 概念来控制元素的尺寸和位置。如果一个元素有 `Layout`，相当于元素创建了 BFC，其尺寸和位置以自身的内容进行计算（如：可通过width、height 来设置自身的宽高）；否则，相当于没有 BFC，它的尺寸和位置由最近的拥有 `Layout` 的祖先元素控制。

IE 浏览器，可能通过如下方法判断元素是否有 `Layout`：

```js
// 返回 true 表示有 Layout，否则就是没有
Element.currentStyle.hasLayout
```

需要注意的是：

* `hasLayout` 是一个只读属性，所以无法通过 Javascript 进行设置，只能通过一些 CSS 属性间接创建；
* 与 BFC 不同的是：有些 CSS 属性是以不可逆方式间接创建 `Layout`，并且默认创建 BFC 的只有 html 元素，而默认 `Layout` 的元素就不只有 html 元素了。

### 哪些元素有 Layout？

```css
<html> <body>
<table> <tr> <th> <td>
<img> <hr>
<input> <button> <select> <textarea> <fieldset> <legend>
<iframe> <embed> <object> <applet> <marquee>
```

### 如何为元素创建 Layout？

* display: inline-block；
* width、height 属性值为除 `auto` 外的任意值；
* float: left|right；
* position: absolute；
* writing-mode: tb-rl；
* zoom 属性值为除 `normal` 外的任意值；
* IE7 还有一些额外的属性：`min-height|min-width` 属性值为任意值；`max-height|max-width` 属性值为除 `none` 外的任意值；`overflow|overflow-x|overflow-y` 属性值除 `visible` 外的任意值（仅用于块级元素）、`position: fixed`。

需要注意：

* 创建 BFC 和 创建 `Layout` 的方式不完全重叠，因此 `Layout` 所引发的问题，很大程度可以理解为在不应该的或没有预料到的地方创建了 BFC 导致的。
* IE6 以前的版本（也包括 IE6 及以后所有版本的混杂模式，其实这种混杂模式在渲染方面就相当于 IE 5.5）， 通过设置任何元素的`width`、`height` 属性值（非 `auto`）都可以创建 `Layout` ； 但在 IE6 和 IE7 的标准模式中的行内元素上却不行，只能设置 `display: inline-block` 才可以。
* `display: inline-block|min-width: 0|min-height: 0` 将不可逆地创建 `Layout`，而在其他属性创建的 `Layout` 可通过上面创建方法，逆向关闭。如：`position: static`，`zoom: normal` 、`max-height|max-width` 属性值为 `none` （IE 7）等。

## 怎样清除浮动呢？

清除浮动，也可以称为闭合浮动。**两者都是为了消除浮动元素产生的影响，即浮动塌陷。**

它们的区别在于：

* **清除浮动：** 对应 CSS 中的 `clear：left|right|both|none`，**只能清除同一 BFC 中，在它前面的元素的浮动。**
* **闭合浮动**：更确切的含义是使浮动元素闭合，即创建 BFC，从而消除浮动塌陷。

确切的说，我们想要达到的效果是闭合浮动，而不是单纯的清除浮动。单纯的清除浮动，并不能解决容器高度塌陷的问题。

比如：我们将上一个示例的背景色模块，加上样式 `clear: both` 清除浮动，该模块的浮动还是会影响其后面的兄弟元素。如下图3：

![image-20200730224651452](https://my-files-1259410276.cos.ap-chengdu.myqcloud.com/md_images/image-20200730224651452.png)

```html
<section class="box_02">
    <div class="item_02" style="clear: both;">
        <div class="container_float"><img src="./images/pins_3333913039.jpg"></div>
        <div class="bg"></div>
    </div>
    ...
</section>
```

[查看DEMO](https://1927344728.github.io/demo-lizh/html/02_float.html?type=3)

再如：我们将上一个示例的背景色模块，加上样式 `display: flow-root` 闭合浮动，创健了新 BFC，那么，它就不会影响其他 BFC 的元素了。如下图4：

![image-20200731003636340](https://my-files-1259410276.cos.ap-chengdu.myqcloud.com/md_images/image-20200731003636340.png)

```html
<section class="box_02">
    <div class="item_02" style="display: flow-root;">
        <div class="container_float"><img src="./images/pins_3333913039.jpg"></div>
        <div class="bg"></div>
    </div>
    ...
</section>
```

[查看DEMO](https://1927344728.github.io/demo-lizh/html/02_float.html?type=4)

### 三种清除浮动的方法

**方法一（摧荐）：** 父元素加伪元素，并给伪元素 `::after` 加 `clear：both` ，据说是最高大上的方法。

\*\*优点：\*\*浏览器支持好，不容易出现怪问题。

**缺点：** 需要全局定义一个清除浮动的 `.clear_float`，且要加在标签上。

```html
<!--定义一个通用清除浮动的样式，在需要的元素中加上-->
<!--clear 属性只有块级元素才有效的，而 ::after 等伪元素默认都是内联元素，所以加 display: block。
<style>
    .clear_float {
        /*对IE6/7的兼容处理，触发 hasLayout*/
        zoom: 1;
    }
    .clear_float:after,
    .clear_float::after {
        clear: both;
        content: '.';
        display: block;
        width: 0;
        height: 0;
        visibility: hidden;
    }
</style>
<section class="box_02">
    <div class="item_02 clear_float">...</div>
    <div class="item_02">...</div>
</section>
```

[查看DEMO](https://1927344728.github.io/demo-lizh/html/02_float.html?type=5)

\*\*方法二：\*\*浮动元素后，新增空标签 `div`，并设置样式 `clear：both` 。

\*\*优点：\*\*通俗易懂，容易掌握。

\*\*缺点：\*\*将添加很多无意义的空标签。

```html
<section class="box_02">
    <div class="item_02">...</div>
    <div style="clear: both;"></div>
    <div class="item_02">...</div>
</section>
```

[查看DEMO](https://1927344728.github.io/demo-lizh/html/02_float.html?type=6)

\*\*方法三：\*\*浮动元素后，新增空标签 `br` 标签，并设置属性 `clear="both"`，与方法二类似：

```html
<section class="box_02">
    <div class="item_02">...</div>
    <br style="clear: both;" />
    <div class="item_02">...</div>
</section>
```

[查看DEMO](https://1927344728.github.io/demo-lizh/html/02_float.html?type=7)

### 闭合浮动的方法

为浮动元素的父元素创建 BFC（为兼容 ie6，还需触发 `hasLayout`，如加： `zoom: 1`）：

* **display: inline-block（推荐）**；
* `overflow: auto|hidden|visible`：据说 `auto` 对 SEO 比较友好， `hidden` 对 SEO 不是太友好。缺点是内部元素宽高超过父元素时，会出现滚动条或者隐藏溢出部分；
* `float: left|right`： 缺点是使得与父元素相邻的元素的布局会受到影响；
* `display: table`： 缺点是盒模型属性已经改变，由此造成的一系列问题，得不偿失；
* `display: flow-root`：缺点是 `Safari` 不支持。

## 常见问题

### clear 属性清除浮动的原理？

使用 `clear` 属性清除浮动，其语法如下：

```css
clear: none|left|right|both
```

官方对 `clear` 属性的解释是：一个元素是否必须移动（清除浮动后）到在它之前的浮动元素下面。我们对元素设置 `clear` 属性只是避免了浮动元素对该元素的影响，实际上，浮动一直还在，并没有清除。

当应用于非浮动块时，它将非浮动块的边框边界移动到所有相关浮动元素外边界的下方；当应用于浮动元素时，它将元素的外边界移动到所有相关的浮动元素外边框边界的下方。这会影响后面浮动元素的布局，后面的浮动元素的位置无法高于它之前的元素。

### BFC 和 IFC 有什么区别？

> Boxes in the normal flow belong to a formatting context, which may be block or inline, but not both simultaneously. Block-level boxes participate in a block formatting context. Inline-level boxes participate in an inline formatting context.
>
> 在普通流中的盒子会参与一种格式上下文，这个盒子可能是块盒也可能是行内盒，但不可能同时是块盒又是行内盒。块级盒参与块级格式上下文（BFC），行内级盒参与行级格式上下文（IFC）。

**BFC 的特性：**

* 在 BFC 中，盒子都是从它的包含块的顶部一个一个的**垂直**放置的。两个相邻盒子的垂直间距决定于 `margin` 属性。在同一 BFC 中，两个相邻块级盒子之间垂直方向上的外边距可能会塌陷的。
* 在 BFC 中，每个盒子的左外边界挨着包含块的左边界（与文档书写模式有关）。即使是存在浮动元素也是如此（即使一个盒子的行盒是因为浮动而收缩了的）除非这个盒子创建了新的 BFC（在某些情况下这个盒子自身会因为浮动而变窄）。

**IFC 的特性：**

* 在 IFC中，盒子水平放置，一个接着一个，从包含块的顶部开始。水平方向的 `margins|borders|padding` 在这些盒子中被平分。这些盒子也许通过不同的方式进行对齐：底部、顶部、基线对齐。矩形区域包含着来自一行的盒子叫做行盒子。
* 行盒子的宽度由浮动情况和它的包含块决定；行内盒子的高度由 `line-height` 的计算结果决定。
* 当一行不够放置的时候会自动切换到下一行，IFC 的高度，由里面最高盒子的高度决定。
* 在行内方向上，各行盒通常具有相同的尺寸，即在水平书写模式下，它们有同样的宽度；在垂直书写模式下，它们有同样的高度。但是，如果同一个块格式化上下文中存在一个 `float`，则这个浮动元素将导致包裹了它的各行框变短。

## 参考链接

[MDN float](https://developer.mozilla.org/zh-CN/docs/CSS/float)

[MDN 块格式化上下文](https://developer.mozilla.org/zh-CN/docs/Web/Guide/CSS/Block_formatting_context)

[CSS魔法堂：hasLayout原来是这样的！](https://segmentfault.com/a/1190000004632071)
