godkun / blog

📝北冥有鱼,其名为鲲
MIT License
183 stars 21 forks source link

可能是最全面最易懂的解析前端浮动的文章 #18

Open godkun opened 6 years ago

godkun commented 6 years ago

写在最前

习惯性去谷歌翻译看了看float的解释: image 其中有一句这样写的:

she relaxed, floating gently in the water

瞬间浮想联翩,一个女神,轻轻地漂浮在水中。开心的拍打着水花,哇靠。。。

不想了,人间不值得,步入正题吧,上面美妙的画面中,我们可以看到,女神还是挤占了水的空间,女神是浮动的。那么来,好了,编不下去了,直接开题吧。。。

我觉得很多人连float是啥意识都不知道,要知道很多特性的原理是和其命名的单词或者字母有密切关联的,不是随便命名的。从名字中可以看到一些当初设计的初衷。

找出问题是关键

问自己三个问题:

第一 浮动会造成什么影响?

第二,如何解决这些因为浮动而造成的影响?

第三,bfc原理?

其实我个人理解,浮动造成的最核心的问题就是破坏了文档流,那么问题来了,float破坏了文档流,为什么还要设计这个api,我查了一些资料最后才知道,这是因为当初设计float的目的是为了能实现文字能够环绕图片的排版功能,也就是我们有时会纳闷的一点:设置浮动后,还是会挤占容器中的文本内容。

比如看下面这段代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>float实现浮动</title>
</head>
<style>
    .z1{
        height: 200px;
        width: 200px;
        float: left;
        text-align: center;
        line-height: 200px;
        background: skyblue;
    }
    .fu {
        width: 400px;
    }
</style>
<body>
<div class="fu clearfix">
    <div class="z1">设置了float为left的图片</div>
    <div class="z2">你看,我没有被浮动哥哥挡住哦,这是一段神奇旅行,一天我遇上了白雪公主</div>
</div>
</body>
</html>

效果图如下:

image

看到这,是不是有些理解了。从上图会发现,即使图片浮动了,脱离了正常文档流,也覆盖在没有浮动的元素上了,但是其并没有将文本内容也覆盖掉,这也证实了float这个api当初被设计出来的主要目的:实现文字环绕图片排版功能。

当想到这时,我突然意识到,其他布局模式是什么样子,然后进行了实验。去掉容器z1float属性,增加了position属性,代码如下

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>absolute实现浮动</title>
</head>
<style>
    .z1{
        height: 200px;
        width: 200px;
        box-sizing: border-box;
        position: absolute;
        text-align: center;
        background: skyblue;
        padding-top: 80px;
        opacity: 0.8;
    }
    .fu {
        width: 400px;
    }
</style>
<body>
<div class="fu clearfix">
    <div class="z1">设置了positon为absolute的图片</div>
    <div class="z2">你看,我被absolute哥哥挡住哦,这是一段神奇旅行,一天我遇上了白雪公主</div>
</div>
</body>
</html>

效果图如下: image

我们可以看到,设置absolute的容器,才是意义上的完全脱离正常文档流。覆盖在当前位置上的所有容器和文本内容之上。对比思考一下,会发现这又证明了float被设计出来的主要目的。如果能理解成这样,我觉得对于不同业务上该用什么方式清除float,或者说该用什么来代替float,将会有个很明确的方向。

其实你会发现,absolutefloat都不会去覆盖掉在他们之前的正常文档流,这应该和浏览器渲染机制有关系,会从上到下依次渲染内容,渲染成功后,就不会因为后续元素浮动而使其被覆盖住(不考虑使用fix等强行覆盖的情况)。

简易代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>清除float浮动</title>
</head>
<style>
    .z1{
        height: 200px;
        width: 200px;
        box-sizing: border-box;
        float: left;
        text-align: center;
        background: skyblue;
        padding-top: 80px;
        opacity: 0.5;
    }
    .z2 {
        background: yellow
    }
    .z3 {
        background: red;
    }
    .z4 {
        background: green;
    }
    .z5 {
        background: pink;
    }
</style>
<body>
<div class="fu">
    <div class="z2">没有设置任何浮动的容器,背景为黄色</div>
    <div class="z3">没有设置任何浮动的容器,背景为红色</div>
    <div class="z1">设置了浮动的元素,opacity为0.5</div>
    <div class="z4">没有设置任何浮动的容器,背景为绿色</div>
    <div class="z5">没有设置任何浮动的容器,背景为粉色</div>
</div>
</body>
</html>

效果图如下: image

从图中的标注和说明我们可以清晰的知道,float不会影响前面已经渲染好的文档,而会影响在其后面将要渲染的文档。那么问题来了,怎样才能消除因为z1的浮动而对z4,z5造成的影响呢?

首先我们要知道,z1这个浮动造成了哪些影响,影响如下:

第一个影响:影响了z4,z5的布局。

第二个影响:影响了父容器的高度,正常父元素的高度是自适应的,高度为其包含的内容总高度,而内部元素的浮动造成了父容器高度塌陷。

第三个影响:父容器高度塌陷了,将会影响和父元素同级的文档布局。

下面的代码可以完美的诠释这些影响:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>清除float浮动</title>
</head>
<style>
    .z1{
        height: 200px;
        width: 200px;
        box-sizing: border-box;
        float: left;
        text-align: center;
        background: skyblue;
        padding-top: 80px;
        opacity: 0.5;
    }
    .z2 {
        background: yellow
    }
    .z3 {
        background: red;
    }
    .z4 {
        background: green;

    }
    .z5 {
        background: pink;
    }
    .z6 {
        color: #fff;
        background: black;
    }
    .z7 {
        color: #fff;
        background: blue;
    }
    .fu {
        /* overflow: hidden; */
    }
</style>
<body>
<div class="fu">
    <div class="z2">没有设置任何浮动的容器, 背景为黄色</div>
    <div class="z3">没有设置任何浮动的容器, 背景为红色</div>
    <div class="z1">设置了浮动的元素, opacity为0.5</div>
    <div class="z4">没有设置任何浮动的容器, 背景为绿色</div>
    <div class="z5">没有设置任何浮动的容器, 背景为粉色</div>
</div>
<div class="z6">和父级元素同级的容器, 没有设置任何浮动, 背景为绿色</div>
<div class="z7">和父级元素同级的容器, 没有设置任何浮动, 背景为绿色</div>
</body>
</html>

效果图如下: image 通过图中的标注我们可以很清晰看到上面提到的三个影响,那么影响也清晰的看到了,下面该如何去解决这些影响呢?

解决思路很重要

要解决这三个影响,需要从两个方向思考:

第一个方向:解决父元素给其同级的元素造成的影响,我比喻成解决外部矛盾

第二个方向:解决父级元素内部的浮动元素对其同级元素的影响,我比喻成解决内部矛盾

俗话说的好,家丑不可外扬,那么来,现在就先解决外部矛盾,怎么解决呢,解决的思想,无非就是让父级元素的高度不再塌陷,把浮动元素的高度算进去。记住一个关键点,这时候,内部矛盾还是存在的。比如浮动元素和其后续的同级元素有高度重叠。

解决外部矛盾

触发bfc

第一个是触发bfc,为什么呢,因为触发bfc后,高度会包括浮动元素的高度。怎么触发呢,可以给父级元素设置overflow:auto;对于其他的触发bfc方式,我就不说了,我主要说一下原理。代码如下

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>清除float浮动</title>
</head>
<style>
    .z1{
        height: 200px;
        width: 200px;
        box-sizing: border-box;
        float: left;
        text-align: center;
        background: skyblue;
        padding-top: 80px;
        opacity: 0.5;
    }
    .z2 {
        background: yellow
    }
    .z3 {
        background: red;
    }
    .z4 {
        background: green;

    }
    .z5 {
        background: pink;
    }
    .z6 {
        color: #fff;
        background: black;
    }
    .z7 {
        color: #fff;
        background: blue;
    }
    .fu {
        overflow: hidden;
    }
</style>
<body>
<div class="fu">
    <div class="z2">没有设置任何浮动的容器, 背景为黄色</div>
    <div class="z3">没有设置任何浮动的容器, 背景为红色</div>
    <div class="z1">设置了浮动的元素, opacity为0.5</div>
    <div class="z4">没有设置任何浮动的容器, 背景为绿色</div>
    <div class="z5">没有设置任何浮动的容器, 背景为粉色</div>
</div>
<div class="z6">和父级元素同级的容器, 没有设置任何浮动, 背景为绿色</div>
<div class="z7">和父级元素同级的容器, 没有设置任何浮动, 背景为绿色</div>
</body>
</html>

效果图如下: image 图中可以很清晰的看出,触发父元素的bfc后,外部矛盾解决了,但是内部的矛盾还没有解决。那么现在就开始解决内部矛盾。怎么解决内部矛盾呢,也就是父元素内部的浮动元素的高度和后面的同级元素的高度有重叠呢。这个时候,我们先不着急解决内部矛盾,我们来看一下,另一种解决外部矛盾的方式。

clear原理

给父元素增加伪元素:代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>清除float浮动</title>
</head>
<style>
    .z1{
        height: 200px;
        width: 200px;
        box-sizing: border-box;
        float: left;
        text-align: center;
        background: skyblue;
        padding-top: 80px;
        opacity: 0.5;
    }
    .z2 {
        background: yellow
    }
    .z3 {
        background: red;
    }
    .z4 {
        background: green;
        /* clear: left */
    }
    .z5 {
        background: pink;
    }
    .z6 {
        color: #fff;
        background: black;
    }
    .z7 {
        color: #fff;
        background: blue;
    }
    .fu {
    }
    .clearfix:after {
        display: block;
        overflow: hidden;
        content: '伪元素的内容哦';
        clear: both;
        height: 0;
        background: slateblue;
    }
</style>
<body>
<div class="fu clearfix">
    <div class="z2">没有设置任何浮动的容器, 背景为黄色</div>
    <div class="z3">没有设置任何浮动的容器, 背景为红色</div>
    <div class="z1">设置了浮动的元素, opacity为0.5</div>
    <div class="z4">没有设置任何浮动的容器, 背景为绿色</div>
    <div class="z5">没有设置任何浮动的容器, 背景为粉色</div>
</div>
<div class="z6">和父级元素同级的容器, 没有设置任何浮动, 背景为绿色</div>
<div class="z7">和父级元素同级的容器, 没有设置任何浮动, 背景为绿色</div>
</body>
</html>

很多人不清楚用伪元素清除浮动的原理是什么,为什么给父元素加这个伪元素,可以清除父元素的浮动。这里我故意在伪元素的content写了一些文本内容,同时加了背景色,有点像基佬色。。。


OK,先看整体效果图吧: image 不出意外,从上图可以看到,外部矛盾被解决了。这只是开始,大家眼睛盯好,继续看下面截图:


image 从图中标注可以看出,为什么伪元素要设置display:block,继续看下一个截图。


image 从上图中可以知道,为什么height要设置成0了。如果content不是空字符串,那么就会在页面中显示内容。但其实清除浮动时,content都会写成空的字符串,如果content里面只设置成''空的字符,那么height也可以不写,包括overflow也可以不写,写heightoverflow都是为了代码的鲁棒性。不过有个很重要,content这个属性,必须要写,不写content,是没法清除浮动的。


最重要的知识点要来了,请看两个截图: image 我故意让content显示出来,会发现伪元素清除浮动的核心原理其实是在给父元素增加块级容器,同时对块级容器设置clear属性,使其能够清除自身的浮动,从而正常按照块级容器排列方式那样排列在浮动元素的下面。同时,父元素的同级元素也会正常排列在伪元素形成的块级元素后面,而不受浮动影响。


下面是干掉clear属性后的截图: image 发现清除浮动失败了,其实可以看出,给父元素增加一个伪元素来清除浮动的本质,是通过

给父元素再加一个块级子容器,当然这个也就是父元素的最后一个块级子容器了。同时给这个块级子容器设置clear属性来清除其浮动,这样这个子容器就能排列在浮动元素的后面,同时也把父元素的高度撑起来了。那么父元素的同级元素也能正常排列了。所以这个子容器不能有高度和内容,不然会影响父元素的布局。

写到这,外部矛盾的解决方式和各自的原理已经说的很清楚了。那么内部矛盾怎么解决呢?

其实,解决内部矛盾的原理和解决外部矛盾的第二种方式的原理是一样的,通过给被浮动影响的第一个元素进行清除浮动,就可以使后面的元素也不会受到浮动影响了。代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>清除float浮动</title>
</head>
<style>
    .z1{
        height: 200px;
        width: 200px;
        box-sizing: border-box;
        float: left;
        text-align: center;
        background: skyblue;
        padding-top: 80px;
        opacity: 0.5;
    }
    .z2 {
        background: yellow
    }
    .z3 {
        background: red;
    }
    .z4 {
        background: green;
        clear:both;
    }
    .z5 {
        background: pink;
    }
    .z6 {
        color: #fff;
        background: black;
    }
    .z7 {
        color: #fff;
        background: blue;
    }
</style>
<body>
<div class="fu">
    <div class="z2">没有设置任何浮动的容器, 背景为黄色</div>
    <div class="z3">没有设置任何浮动的容器, 背景为红色</div>
    <div class="z1">设置了浮动的元素, opacity为0.5</div>
    <div class="z4">没有设置任何浮动的容器, 背景为绿色</div>
    <div class="z5">没有设置任何浮动的容器, 背景为粉色</div>
</div>
<div class="z6">和父级元素同级的容器, 没有设置任何浮动, 背景为绿色</div>
<div class="z7">和父级元素同级的容器, 没有设置任何浮动, 背景为绿色</div>
</body>
</html>

效果图如下: image

给内部元素设置clear:both;清除浮动后,会直接解决内部矛盾和外部矛盾。可能会有人想,如果z4容器后面又有一个浮动元素呢,这里我不想再解释了,因为可递归得出原理都是一样的,但是吧,我还是上个代码分析一下吧:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>清除float浮动</title>
</head>
<style>
    .z1{
        height: 200px;
        width: 200px;
        box-sizing: border-box;
        float: left;
        text-align: center;
        background: skyblue;
        padding-top: 80px;
        opacity: 0.5;
    }
    .z2 {
        background: yellow
    }
    .z3 {
        background: red;
    }
    .z4 {
        background: green;
        clear:both;
    }
    .z5 {
        background: pink;
        /* clear:both */
    }
    .z6 {
        color: #fff;
        background: black;
    }
    .z7 {
        color: #fff;
        background: blue;
    }
    .fu {
        overflow: auto;
    }
</style>
<body>
<div class="fu">
    <div class="z2">没有设置任何浮动的容器, 背景为黄色</div>
    <div class="z3">没有设置任何浮动的容器, 背景为红色</div>
    <div class="z1">设置了浮动的元素, opacity为0.5</div>
    <div class="z4">没有设置任何浮动的容器, 背景为绿色</div>
    <div class="z1">设置了浮动的元素, opacity为0.5</div>
    <div class="z5">没有设置任何浮动的容器, 背景为粉色</div>
</div>
<div class="z6">和父级元素同级的容器, 没有设置任何浮动, 背景为绿色</div>
<div class="z7">和父级元素同级的容器, 没有设置任何浮动, 背景为绿色</div>
</body>
</html>

效果图如下几张截图:

父元素没有清除浮动,外部矛盾,内部矛盾都存在 image

父元素使用bfc清除浮动,外部矛盾解决,内部矛盾还存在 image

通过给父元素中的浮动元素后面的第一个同级块级元素设置clear清除浮动,内部矛盾解决,外部矛盾也解决。 image

对于clear还有leftright,这个就不说了,api的事情,正常both就可以了。写到此,差不多要结束了。最后再总结一下吧:

不同业务中可能需要不同的清除浮动的方式,不论选择哪一种方式,都避不开外部矛盾和内部矛盾,你的业务需要保留内部矛盾,只解决外部矛盾,还是外部矛盾和内部矛盾都解决。这些得需要根据业务的特点来决定。其次,是使用bfc还是clear还是伪元素,使用bfc的话使用哪种方式去触发。这也是根据业务的特点来决定。

文末的可爱声明:如果转发或者引用,请贴上原链接,尊重一下熬夜完成的劳动成果😂。文章可能有一些错误,欢迎评论指出,也欢迎一起讨论。文章可能写的不够好,还请多多包涵。为了前端,我也是操碎了心,人生苦短,我学前端,多一点贡献,多一分开心~