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

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

查看文本环绕DEMO

什么是浮动?

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

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

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

浮动会造成哪些影响呢?

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

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

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

    注意: 浮动元素之前的元素将不会受到影响。

  • 对父元素: 父元素在获取高度计算时,会忽略浮动元素,即不计算浮动元素高度。这就是浮动塌陷,也称高度塌陷现象。

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

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

块级格式化上下文的特性与层叠上下文相似,都是指独立的空间,不过,块级格式化上下文是 x、y 的独立空间,而层叠上下文是 z 轴上的独立空间。

BFC 有哪些特性?

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

BFC 的三个特性:

  • BFC 会阻止外边距折叠(CSS探索系列之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-countcolumn-width 不为 autocolumn-count: 1

  • column-span: all。即使该元素没有包裹在一个多列容器中(标准变更,Chrome bug)。

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

什么是 hasLayout?

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

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

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

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

需要注意的是:

  • hasLayout 是一个只读属性,所以无法通过 Javascript 进行设置,只能通过一些 CSS 属性间接创建;

  • 与 BFC 不同的是:有些 CSS 属性是以不可逆方式间接创建 Layout,并且默认创建 BFC 的只有 html 元素,而默认 Layout 的元素就不只有 html 元素了。

哪些元素有 Layout?

<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), 通过设置任何元素的widthheight 属性值(非 auto)都可以创建 Layout ; 但在 IE6 和 IE7 的标准模式中的行内元素上却不行,只能设置 display: inline-block 才可以。

  • display: inline-block|min-width: 0|min-height: 0 将不可逆地创建 Layout,而在其他属性创建的 Layout 可通过上面创建方法,逆向关闭。如:position: staticzoom: normalmax-height|max-width 属性值为 none (IE 7)等。

怎样清除浮动呢?

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

它们的区别在于:

  • 清除浮动: 对应 CSS 中的 clear:left|right|both|none只能清除同一 BFC 中,在它前面的元素的浮动。

  • 闭合浮动:更确切的含义是使浮动元素闭合,即创建 BFC,从而消除浮动塌陷。

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

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

<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

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

<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

三种清除浮动的方法

方法一(摧荐): 父元素加伪元素,并给伪元素 ::afterclear:both ,据说是最高大上的方法。

**优点:**浏览器支持好,不容易出现怪问题。

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

<!--定义一个通用清除浮动的样式,在需要的元素中加上-->
<!--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

**方法二:**浮动元素后,新增空标签 div,并设置样式 clear:both

**优点:**通俗易懂,容易掌握。

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

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

查看DEMO

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

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

查看DEMO

闭合浮动的方法

为浮动元素的父元素创建 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 属性清除浮动,其语法如下:

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

MDN 块格式化上下文

CSS魔法堂:hasLayout原来是这样的!

最后更新于