Skip to content

浮动也是网页布局的一种方式,浮动其实是为了做文字环绕效果而出现的布局,如下图所示:

![Pasted image 20240213114439](/notes-assets/01.HTML&CSS/2.CSS基础/assets/Pasted image 20240213114439.png)

浮动这个东西就是为了做文字环绕图片效果而出现的布局,但后来由于浮动有一个很神奇的功能,就是可以把盒子横过来排列,所以浮动后来就经常被用来实现横向排列的布局。

![Pasted image 20240213114730](/notes-assets/01.HTML&CSS/2.CSS基础/assets/Pasted image 20240213114730.png)

例如上图中淘宝的导航栏,就是使用浮动来将这些列表横向排列。

这里可能有的读者会有疑问,我们的行块盒其实也可以实现横向排列,同时由于是行块盒,是具有块盒的一些特点的,确实可以很好实现这样的横向排列,那为什么我们要用浮动来实现这样的效果呢?实际上是因为行块盒存在非常多的问题,一个很显著的问题就是 空白折叠,这一点在行块盒部分提到过,读者可参考 7.盒模型 。

总之,行块盒虽然可以实现这样的横向排列效果,但是由于行块盒的一些问题,我们一般不会使用行块盒去实现横向排列。网页中会存在大量的横向排列,这些横向排列都可以用浮动来做。

由于行块盒做横向排列效果不理想,浮动本身又不是专门用来干横向排列的,但横向排列又是一个很常见的布局,所以 CSS 3 提供了弹性盒、网格等布局很好地实现了横向排列这一功能,所以理论上来说我们是没有必要使用浮动去实现横向排列的,直接使用 CSS 3 的弹性盒即可。但浮动这玩意儿其实老早就有了,兼容性相当好,所以大型公司的网页为了兼容性考虑也还会选择浮动来实现横向排列。

我们修改一个元素的 float 属性,就可以让其浮动,将 float 属性设置为 left 即为左浮动,设置为 right 则为右浮动。默认值为 none,即不浮动。

当元素变成浮动之后,其不再是常规流布局,也就不再遵循常规流布局的规则,左浮动会让元素靠上靠左排列,右浮动会让元素靠上靠右排列。由于浮动元素并不会再按照常规流布局,我们也称其脱离了常规流,或者说脱离文档流。注意,这里说到的靠上靠左和靠上靠右排列,也是基于其包含块的,浮动元素的包含块和常规流一样,就是其父元素的 content-box 区域。

接下来看一下效果

![Pasted image 20240213132936](/notes-assets/01.HTML&CSS/2.CSS基础/assets/Pasted image 20240213132936.png)

如果不设置浮动,默认是常规流布局,三个块盒都独占一行,从上往下依次排列的,此时我让这三个元素的 float 属性设置为 left,即让它们向左浮动。

![Pasted image 20240213134403](/notes-assets/01.HTML&CSS/2.CSS基础/assets/Pasted image 20240213134403.png)

可以看到,元素就横过来排列了。对于浮动元素,其实并没有行盒和块盒这样一个概念,只要元素设置了浮动,那么它一定就是一个块盒,哪怕它不是块盒,它也会被强制变为块盒,即 display 属性会被设置为 block。所以我们就可以这样理解,一个包含块内的所有浮动元素,都会按照从上往下,从左往右(从右往左)的顺序进行排列。

我们可以验证

![Pasted image 20240213134813](/notes-assets/01.HTML&CSS/2.CSS基础/assets/Pasted image 20240213134813.png)

对于这三个行级元素,当我们给它设置左浮动之后,效果如下所示:

![Pasted image 20240213134854](/notes-assets/01.HTML&CSS/2.CSS基础/assets/Pasted image 20240213134854.png)

此时 display 属性自动变成了 block,即变成了块级元素。此时我们还可以给它设置宽高等属性。

所以我们现在可以得出结论,不管元素是什么东西,只要它浮动了,我就可以把它称为浮动元素,它的 display 属性就一定是 block,就不再存在行盒或者行块盒这样的概念。其实行盒和行块盒只存在于常规流。我把浮动后的元素统称为浮动元素。对于常规流来说,块盒的 margin-box 的宽度是必须占满整个包含块宽度的,这样才能保证独占一行。但是浮动元素不同,对于浮动元素而言,如果不指定宽高,即设为 auto 属性的时候,其宽高始终是适应内容的,而不会像常规流那样扩充宽度来独占一行。

![Pasted image 20240213140316](/notes-assets/01.HTML&CSS/2.CSS基础/assets/Pasted image 20240213140316.png)

可以看到,在不设置宽高的情况下,即宽高为 auto 的情况下,尽管浮动元素的 display 属性为 block,它也不会像常规流的块盒那样扩充宽度来沾满一行,其宽高都是跟随内容的宽度而变化的。

在常规流还提到过,如果对一个块盒定宽之后,将横向 margin 设置为 auto,就会用 margin 去把包含块中一行剩余的空间吸收完,以此达到独占一行的效果,但是浮动元素并没有独占一行这样的特性,将其横向 margin 设置为 auto 的时候,就不会像常规流中的块盒那样扩充 margin。对浮动元素而言,将 margin 设置为 auto 就等同于把 margin 设置为 0。

对于浮动元素而言,其排列顺序均按照从上往下,从左往右(从右往左)进行排列。

![Pasted image 20240213165547](/notes-assets/01.HTML&CSS/2.CSS基础/assets/Pasted image 20240213165547.png)

![Pasted image 20240213165621](/notes-assets/01.HTML&CSS/2.CSS基础/assets/Pasted image 20240213165621.png)

其实从这里我们可以看到,浮动其实很简单,左浮动就是从上往下从左往右排列,右浮动就是从上往下从右往左排列。以上都是基于一个包含块中只有浮动盒子的情况。我们知道,当我们给一个盒子设置了 float 属性,它就变成了浮动盒子,如果一个包含块内所有的元素都设置为浮动盒子,排列方式就像上面那样非常简单。但如果只有部分元素是浮动元素,其他元素依旧是常规流,那情况就比较复杂。

要说明白这个复杂的情况,有必要从另一个角度来理解浮动,所谓浮动,其实就是把盒子浮起来,上升到一个更高的层次。这里可以把浮动理解为以下两步:

  1. 将元素的 display 属性设置为 block,放到标准流中去。
  2. 将元素从这个位置浮起来,变成浮动元素。

这里的浮起来,其实就是离我们更近一点,下面用例子来解释。

![Pasted image 20240213172658](/notes-assets/01.HTML&CSS/2.CSS基础/assets/Pasted image 20240213172658.png)

我们看这个例子,第一个常规盒肯定按常规流来排列,也就排到最左上角。我把第二个和第三个盒子都设置左浮动,即形成左浮动盒 1 和左浮动盒 2。我们观察到,左浮动盒 1 和左浮动盒 2 是从常规流盒子下面开始横向排列的。我们按照前面的两个步骤来分析一下,首先看左浮动盒 1,先把左浮动盒 1 变成块盒,放到标准流中去,然后再浮到上面来,由于我们放到标准流中去的时候这个左浮动盒 1 就在图中这个位置,所以左浮动盒 1 浮起来之后只是离我们更近了,并没有发生位置上的改变。而左浮动盒 2 也一样,先把它变成块盒,然后按照标准流放进去,这个时候应该位于左浮动盒 1 的下面,此时把它浮起来,由于左浮动盒 1 把它压住了,浮不起来,所以左浮动盒 2 就会按照浮动的排列规则在浮动这一层从左往右进行排列。

![Pasted image 20240213173236](/notes-assets/01.HTML&CSS/2.CSS基础/assets/Pasted image 20240213173236.png)

再看这个例子,我在下面再加了一个常规流盒子,注意看盒子模型,先不要看文字。这个常规流盒子实际上是在左浮动盒 1 下面的。也就是说由于浮动流的盒子被浮上去了,常规流是看不到浮动流盒子的,会紧贴上一个常规流盒子进行排列。但是这里很明显有一个问题,虽然这个常规流盒子在浮动盒下面,但里面的文字却跑到外面来了,也就是说这个左浮动盒 1 将我这个常规流盒子的文字挤走了。

这里其实就有一个规则,浮动盒子在常规流的上面,只能压住常规流的盒子本身,压不住常规流盒子里面的文字。文字会被自动挤出来。这条规则也很容易理解,浮动这个东西本身就是用来做文字环绕效果的,肯定是希望文字环绕这个浮动元素,如果浮起来的元素都把下面常规流的文字压住了,那谈何环绕呢。

综上,我们就可以得出结论,浮动是基于常规流位置浮动起来的,即先放到常规流内再浮动起来。而常规流布局是会忽视掉浮动元素的,但为了浮动上来的元素不会挡住下面常规流的文字,会将文字强制挤出来,但盒子本身依旧是被挡住的。还有一点值得注意,行盒本身应该被视作一段文本,当行盒被浮动元素挡住后,行盒整体会被挤到外面,而不会像块盒那样只挤出内容。

接下来用一个例子最后解释一下这些内容

html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        * {
            margin: 0;
            padding: 0;
        }
        .parent div {
            border: 1px solid;
            height: 100px;
            width: 100px;
        }
        .parent .child {
            float: left;
        }
        .parent::after {
            content: "";
            display: block;
            clear: both;
        }
    </style>
</head>
<body>
    <div class="parent">
        <div>常规块盒</div>
        <div class="child">左浮动盒1</div>
        <div class="child">左浮动盒2</div>
        <span>常规行盒</span>
        <div>常规块盒</div>
    </div>
</body>
</html>

效果如下:

![Pasted image 20240213205147](/notes-assets/01.HTML&CSS/2.CSS基础/assets/Pasted image 20240213205147.png)

首先,第一个常规块盒是常规流,按照常规流的布局排在最左上角。第二个盒子和第三个盒子分别是左浮动盒 1 和左浮动盒 2,这两个左浮动盒设置了左浮动,按照前面我们提到的,应该先将其设置为块盒,按照常规流的布局先排上去,应该排在第一个常规块盒的下面,这一点我相信读者不应该有疑问。然后将它原地浮起来。然后是左浮动盒 2,左浮动盒 2 也一样,先将他设置块盒,放到常规流中,此时应该在左浮动盒下面,然后将它原地浮起来,但是由于它被左浮动盒 1 挡住了,浮不起来,所以就会按照左浮动的排列规则,从左到右从上到下依次排列。然后是常规行盒,由于常规流是看不到浮动元素的,所以这个常规行盒按照常规流的排列规则,应该排列到左浮动盒 1下面,但如果这样这个行盒中的文字就被遮住看不到了,为了保证文字不被浮动元素遮住,这个常规行盒会被强制挤到旁边,也就是一个我们可以看到的位置。而对于最后一个常规块盒,按照常规流布局的规则,应该放到常规行盒下面,这也是符合我们预期的。但是我们可以观察到,这个行盒的文字位置似乎不对,文字跑到了最底部,这也是由于浮动元素不能遮住文字,所以所有的文字都会被挤到我们能看到的区域。

浮动元素只能遮住常规流中的块盒,不能遮住文本,对于行盒而言,其本身就可以被视作文本,所以整个行盒元素都会被挤开,所以我们可以说浮动元素是不能遮住行盒元素的。但浮动元素可以遮住块盒,但它也不能遮住块盒内的文本,块盒内的文本也会被强制挤到一个我们可以看到的位置。

浮动元素遮不住文本,当浮动元素遮住文本的时候,文本内容会被直接挤到一个我们能看到的位置,并且会环绕这个浮动元素,这就是通过 float 实现文字环绕的原理。

浏览器如何实现浮动元素挤开块盒内的文字

![Pasted image 20240213211048](/notes-assets/01.HTML&CSS/2.CSS基础/assets/Pasted image 20240213211048.png)
我们可以看到,对于行盒而言,浮动元素是会直接给他一整个挤走的,这是把这一整个元素寄出去,这其实很好理解,我们可以直接理解为行盒元素可以看到浮动元素,并且会排在浮动元素后面,或者说会避开浮动元素进行排列。
![Pasted image 20240213211327](/notes-assets/01.HTML&CSS/2.CSS基础/assets/Pasted image 20240213211327.png)
但是对于块盒而言,它看不到浮动元素,所以我们可以看到这个块盒实际上是被上面的浮动元素压住的,但为什么浮动元素能够做到单单把里面的文字给挤走的。凭什么这个元素本身都看不到浮动起来的盒子,元素里面的文字好像反而能看到浮动起来的盒子。这其实涉及到匿名盒子的问题,直接写在块盒下面的文本,CSS 引擎会为它填上一个匿名行盒来包裹它,这个盒子我们是看不到的,但是确实有这个东西。正因为这个块盒里面的文本实际上是被一个匿名行盒包裹起来了,所以其实这里面的文本是可以看到浮动元素的。也就会被挤开。

匿名盒子

前面我们接触的所有盒子都是我们写一个元素生成一个盒子对吧,但实际上,CSS 会为我们生成一些盒子,这鞋盒子并不能被选中,它只是为了渲染效果而自动生成的。
匿名盒子也有匿名行盒和匿名块盒。
CSS 有这几个原则:

  • 行盒容器内只能存放行盒元素
  • 块盒容器内只能存放行盒或块盒元素,不允许二者同时存在
  • 所有的文本均应该放在行盒容器内

我们写的页面基本上不可能完全遵守上面的原则,此时 CSS 就会生成一些匿名盒子让我们满足规范。

首先,所有的文本都应该放在行盒容器内,但我们明明经常把文本直接放在块盒容器内呀,其实 CSS 引擎会自动把那些直接写在块盒容器内的文本用一个匿名行盒包裹起来,这个匿名行盒是自动生成的,无法被选择器选中,所以我们看不到。(这也是为什么浮动的时候块盒容器内的文本会被挤出去的原因,因为行盒是可以看到浮动元素的,所以会避开浮动元素排列,看起来就像是被浮动元素挤出去了一样)

块盒容器内只允许出现行盒或块盒,如果都是行盒或者都是块盒,那这是正确的,不需要添加额外的匿名盒子,那如果同时包含了行盒和块盒,就会把相邻的行盒都用一个匿名块盒包裹起来,从而让所有子元素都是块盒。
<div> <span></span> <span></span> <p></p> </div>
如上面这个结构所示,div 里包含了两个 span 和一个 p,p 元素是块盒,span 则是行盒,由于 div 作为一个块盒容器,里面只能包含行盒或者块盒,CSS 引擎会将前面两个 span 元素用一个匿名块盒包裹起来,此时 div 里面就只有两个块盒元素了。

接下来是行盒容器里不能放块盒容器,只能放行盒容器,这一点其实我们开发中就已经是一个规范了,我们开发一般都不允许在行盒里面放块盒。但实际上它是可以放的,如下所示:<p style="display=inline"> <span>span1</span> <span>span2</span> <div>div1</div> <span>span3</span> <div>div2</div> <span>span4</span> </p>
在这个例子中,我让 p 元素强制变成了行盒元素。此时这个行盒元素里面存放了两个块盒元素。这种情况的处理其实比较复杂,此时 span 1 和 span 2 会被共同包裹在一个匿名块盒子中,span 3 会被包裹在一个匿名块盒子中,span 4 会被包裹在一个匿名块盒子中,而 div 1 和 div 2 这两个块盒实际上和前面提到的匿名块盒是一种兄弟盒子的关系。具体可以参考下面的网址:
视觉格式化模型
视觉格式化模型 - CSS:层叠样式表 | MDN

^77be23

前面我们提到,在常规流中,块盒是看不到浮动盒子的,所以块盒在排列的时候是会直接无视浮动盒子。但是行盒是可以看到浮动盒子的,在排列的时候会避开浮动盒子,保证浮动盒子不会把行盒压住。

块盒无视浮动盒子这一特性往往会带来一些问题,前面我们提到过,块盒的高度设置为 auto 的时候,其高度是自动计算的,或者说是被这个块盒里面的内容撑开的,里面的内容有多高,它自己就有多高。但是由于块盒看不到浮动元素,所以在计算高度的时候完全不会考虑浮动元素,这就会导致一些问题,看下面的例子:

html
<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <title>box</title>
    <style type="text/css">
        * {
            margin: 0;
            padding: 0;
        }
        .container {
            border: 1px solid black;
        }
        .item {
            height: 100px;
            width: 100px;
            border: 1px solid black;
            float: left;
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="item"></div>
        <div class="item"></div>
        <div class="item"></div>
        <div class="item"></div>
        <div class="item"></div>
        <div class="item"></div>
    </div>
</body>
</html>

![Pasted image 20240214181010](/notes-assets/01.HTML&CSS/2.CSS基础/assets/Pasted image 20240214181010.png)

我们可以看到,container 元素没有设置高度,默认是 auto,即高度被里面的内容撑开,但是里面的几个 item 都是浮动元素,这个 container 是看不到的,所以高度就变成了 0。这个问题非常常见,我们很形象得称其为浮动导致的高度坍塌。这个问题的解决方案非常固定,就是清除浮动。我们知道,常规流的块盒在排列的时候是会无视浮动元素的,如所示。

html
<div class="container">
        <div class="item"></div>
        <div class="item"></div>
        <div class="item"></div>
        <div class="item"></div>
        <div class="item"></div>
        <div class="item"></div>
        <p>我是一个P元素</p>
    </div>

![Pasted image 20240214182452](/notes-assets/01.HTML&CSS/2.CSS基础/assets/Pasted image 20240214182452.png)

可以看到这个 p 元素是一个块盒,是直接无视浮动元素置顶排列的。我们现在考虑的就是如何让这个 p 元素看到浮动元素,也就是排列到浮动元素的下方,此时这个 p 元素就会作为常规流的块盒撑开 container。可以看到,我们解决这个问题的思路其实是,由于常规流的块盒的自动高度在计算的时候只能看到常规流布局中的元素,看不到浮动元素,所以我们给他添加上一个常规流的块盒,让这个块盒去撑开 container。

接下来要解决的就是如何让这个 p 元素看到浮动,其实很简单,CSS 中提供了 clear 属性来干这个事,这个属性用于清除浮动,我们需要知道这个属性的三个值,分别是 left, right , both。

将 clear 设置为 left 表示清除左浮动,设置为 right 表示清除右浮动,设置为 both 表示左右浮动都清除,这里的清除浮动并不是指不浮动。我们知道,常规流的盒子在排列的时候是不会看到浮动盒子,是会被浮动盒子压住的,如果将 clear 设置为 both 那这个常规流的盒子就不会被浮动元素压住,会自动排列到浮动元素下方。也就是说此时常规流的块盒就可以看到浮动盒子了。如果设置为 left 就表示只能看到左浮动的盒子,如果设置 right 就表示只能看到右浮动的盒子。

接下来给 p 元素加上 clear:both;

html
<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <title>box</title>
    <style type="text/css">
        * {
            margin: 0;
            padding: 0;
        }
        .container {
            border: 1px solid black;
        }
        .item {
            height: 100px;
            width: 100px;
            border: 1px solid black;
            float: left;
        }
        p {
            clear: both;
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="item"></div>
        <div class="item"></div>
        <div class="item"></div>
        <div class="item"></div>
        <div class="item"></div>
        <div class="item"></div>
        <p>我是一个P元素</p>
    </div>
</body>
</html>

![Pasted image 20240214200202](/notes-assets/01.HTML&CSS/2.CSS基础/assets/Pasted image 20240214200202.png)

可以看到,此时这个 p 元素清除浮动,也就能看到浮动元素了,排列的时候就会避开浮动元素,也就排列到浮动元素的下方了,同时也撑开了外面的 container。

一般来说,我们都会使用 both,如果设置为 left 就只能看到左浮动,也就会排列在前面所有左浮动盒子下方,设置为 right 就只能看到右浮动,也就会排列在前面所有右浮动盒子下方。

![Pasted image 20240214210442](/notes-assets/01.HTML&CSS/2.CSS基础/assets/Pasted image 20240214210442.png)

此时我们只要让 p 元素里面啥也没有,高度为 0,也就完美解决了这个高度塌陷的问题,外层的 container 也就可以包裹住浮动元素了。

当然,其实我们不太会像这样专门新加一个元素去解决高度塌陷的问题,我们会使用 after 伪元素来解决这个问题。假设是 container 容器产生了高度塌陷,我们使用下面这个样式就可以解决:

css
.container::after {
            content: "";
            display: block;
            clear: both;
}

![Pasted image 20240214210939](/notes-assets/01.HTML&CSS/2.CSS基础/assets/Pasted image 20240214210939.png)

可以看到,此时这个 after 伪元素就替代了刚才那个 p 元素,解决了这个高度塌陷的问题。

伪元素默认是行盒,清除浮动是让元素可以看到浮动元素,但行盒本来就可以看到浮动元素,会自动避开浮动元素,排列到左浮动元素的右边。所以清除浮动其实只作用于块盒。因此这里要把伪元素的 display 属性设置为 block

注意,外边距合并只发生在常规流。浮动流并不存在边距折叠的问题。

浮动的具体规则

浮动具体规则可以参考 MDN,这里给出常用的几点,以应对大部分场景

  • 左浮动向上,向左排列
  • 右浮动向上,向右排列
  • 浮动盒子的顶边不得高于上一个浮动盒子的顶边
  • 若剩余空间无法放下浮动的盒子则该盒子向下移动,直到具备足够的空间能容纳盒子,然后再向左或向右移动。

以笔记为镜,可以知得失