大家好,很高兴又见面了,我是能说会道,技术一流的一灯小雨。
今天和您一起探讨一下CSS的float属性。如果您做过网页开发,想必您对这个属性一定非常的熟悉,因为这是网页中实现图文排版的必备属性之一。float的字面意思是浮动,这个是一个相对的概念,之所以有浮动,是因为有文档流的存在。关于两者的关系,我们可以不从两者的逻辑意义,而从它们的物理意义上去理解。流体上浮动的元素,是不是听起来很有意思呢?
文档流
文档流我们可以认为是一种流体存在,浮动元素就是漂浮在文档流之上的固定元素。它不属于文档流,却能影响文档流走向。我们可以试想一种场景:一条自西向东流向大海的小河,小河左岸栓着一艘小船,那么在CSS中,小河代表的就是文档流,小船代表的就是浮动元素。那么,我们不难想象,河水在流动过程中凡是与船体相接触的元素都要改变流向,绕着船身走。在CSS中,浮动和文档流正是这样一种关系。没有河水,船就会掉到地上。在讨论浮动之前,就一定要搞清什么是文档流。那么什么是文档流呢?话不多说,请看代码:div{
height:100px;
border: 1px solid red;
}
body:
<div></div>
<div></div>
<div></div>
关于文档流的说法,官方给的定义非常模糊。业界流行的说法也各不一样,到目前为止,至少有三种关于文档流的说法。最早流行的说法是指block元素自顶向下的布局规则。如上面代码所呈现的那样,它表示了元素布局的走向。另一种说法是文档流表现为元素会自动填充父容器的宽度。这也很好理解,我们将一小把沙子慢慢漏到杯中,沙子会在杯底形成一个小沙堆,如果我们想要沙堆均匀铺满整个杯底,就需要人为的摇一摇。如果我们把沙子换成水(注意!这是流体),则不管怎么倒,它的液面总是平的,也就是说它总是自动铺满整个父容器。关于这个特性,上面的示例代码也有体现:我们看到示例中的div只定义了高度,并没有定义宽度,实际的显示效果是所有的div宽度都是自动铺满了整个页面。还有另一种说法,说文档流指的是行内元素依次排列,空间不够了则换行进行排列(注意!这是CSS自动帮你做的)。请看代码:
style:
div{
width:100px;
border:1px solid red;
word-wrap:break-word;
}
body:
<div>aaaaaaaaaaaaaaaaaaaaaaaaaaaaa</div>
CSS相比于其他语言的不同之处在于它不是一门编程语言,而是一门排版语言。官方给的属性说明中仅仅告诉你使用方法,并没有告诉你底层的实现细节。更为特殊的是在CSS中并不强调单个属性的功能,而是强调属性之间的相互作用,相同的属性值在不同的上下文中会产生不一样的效果。如果套用编程语言中的概念,那么绝大多数的CSS属性都是会产生副作用的。这也正是很多人在学习CSS时遇到的困惑:虽然语法简单,但却很难掌握要领。在调试CSS时,你一定也遇到过这种情况:这个属性改一改,那个参数动一动,最后达到了效果,至于究竟是那个参数起了关键作用,根本就搞不清楚(调个效果调一天,还被领导质疑能力,也不想搞清楚了!)。上述三种关于流的定义从不同的角度描述了CSS的布局特性,或者我们可以来个综合的定义:自上而下,自动平铺,自动换行。那么关于流,就先说这些,你是否有自己的想法呢?欢迎给我留言吧。咱么开始正式介绍今天的主角:float。
float
在介绍float的特性之前,你首先要知道它的作用的什么。float的历史非常古老,最初就是为了实现文字环绕效果。它带来的副作用是父元素高度塌陷。高度塌陷很容易被人理解为这是float设计的一个缺陷,实际上不是,高度塌陷是实现文字环绕的必要条件。你可以脑补一下:如果父元素高度不塌陷,后面的元素怎么顶上去以达到文字环绕效果呢?如果你能想通这一点,你就理解float的精髓,也明白了float的设计者最初的本意。很多人都知道如何用float实现文字环绕效果,也知道如何清除float造成的高度塌陷副作用。如果我们仅仅用float做环绕效果,那么没有问题,你所掌握的那点float知识就完全够用了。实际上,float早已广泛用于日常页面的布局当中,float的作用已经远远超出了最初设计者的意图。那么关于float在布局中的特性,你又了解多少呢?包裹性
包裹性指的是父容器的宽高大小始终随着子元素的宽高的变化而变化。父元素对子元素呈现包裹的态势。举一个例子,二战的时候,曾经有一种战斗机把油箱的材料做成弹性橡胶的,这种油箱具有很好的伸缩性,会随着油量的大小改变自身大小。这样油箱对油就呈现了紧紧包裹的态势,这样的油箱非常地安全,即使被子弹击中也不会着火,因为油箱和油之间没有间隙,光是产生高温而没有空气是不会着火的。我们也可以将float的包裹性理解为具有弹性特征,能随着子元素的大小而放大缩小。在CSS中,我们可以找到一系列的具有包裹性的元素,总结起来大致可以这样认为:所有display:inline系列的都是包裹性元素。那么,既然float元素在包裹性上和inline系列元素表现一致,我们是不是可以认为float也是inline元素呢?请看代码:
style:
div{
border:1px solid red;
}
body:
<div>aaaaaaaaaaaaaaaaaaaaaaaaaaaaa</div>
这里没有float,我们可以看到元素的边框铺满了整屏,体现了流特征,没有对子元素表现出包裹性。我们再看下面代码:
style:
div{
border:1px solid red;
float:left;
}
body:
<div>aaaaaaaaaaaaaaaaaaaaaaaaaaaaa</div>
加入float之后,父元素边框被限制在内容区域,失去了流特征,没有铺满整屏,体现出了包裹性。这里的实现和下面的代码是等价的:
style:
div{
border:1px solid red;
display:inline-block;
}
body:
<div>aaaaaaaaaaaaaaaaaaaaaaaaaaaaa</div>
可以看到,我们用inline-block实现了和float一样的效果。那么,我们的问题是:float和display:inline-block是存在必然的联系,还是这种效果的一致性仅仅只是巧合? 请看下面的代码:
style:
.flow{
border: 1px solid red;
width: 150px;
word-wrap: break-word;
}
.flow>span{
float: right;
color: red;
}
body:
<div class="flow">aa<span>b</span>aaaaaaaaaaaaaaaa</div>
<div class="flow">aaaaaaaaaaaaaaaaa<span>b</span>a</div>
实际显示的效果是,float元素会跟随行内元素换行而跟着换行。这是怎么回事呢?
浮动锚点
关于浮动的作用细节是这样的:元素一旦产生浮动,那么它自身的display属性会发生一些变化,它会将自身变为一个类inline-block的元素。inline-block元素实际包含两种盒模型,外层的IFC盒,负责元素与其他元素之间的对齐和定位。内层的BFC盒,负责处理元素的内容。那么元素浮动之后,在元素的内容之外就产生了一个IFC定位盒。此盒就是float的浮动锚点。float产生的IFC定位盒会对齐内容盒的顶部并自动排入当前的行内格式化上下文。这就是高度塌陷产生的底层原理。接下来,元素通过float属性值寻找块级格式化上下文(BFC)的边缘进行对齐。那么到此为止,整个浮动的过程就算完成了。浮动锚点我们可以把它形象地理解为小船上的锚,把锚抛到什么地方,小船就会定位到什么地方。在CSS的float属性中,锚的落点是与当前的行内格式化上下文(IFC)密切相关的。因此,我们就看到了上文代码所呈现的效果:浮动元素跟随行内文本的换行而换行了,因为浮动锚点发生了变化。如果你对于以上的解释还有疑惑,又或者你对於某些概念还有些模糊,尤其是IFC,以至于你还是不明白float的作用细节。那么我在此用大白话再次向你解释一下上文代码中两种浮动的具体细节:首先,我们设置了父元素的word-wrap: break-word,这给容器设置了一个对单词强行中断进行换行的环境,CSS解释器在解释容器中的元素的时候发现元素的宽度不足以单行呈现内容,于是强行中断单词进行换行。换行导致的结果是两个元素里面的浮动锚点分别在第一行和第二行,于是两个浮动就产生了不一样的结果。了解了这些细节,您是否对浮动有了更深入的理解了呢?关于浮动,一个比较普遍的错误认知就是:一旦元素产生浮动,它的位置就是固定的。那么,在了解了浮动锚点之后,您是否还这样认为呢?那么接下来,我们再深入探讨一下IFC吧,或许它能更好地帮你深入理解浮动锚点和它的作用细节。IFC
很少有专门介绍IFC的文章,多数的教程都不约而同地避开了这个主题。在实在绕不开的情况下也只是简单地提一下,大体说来,是说IFC相对BFC来说对布局影响没那么重要,而且IFC的可控属性非常少,没什么好讲的。既然IFC可控属性非常少,那应该更好讲嘛,何不在讲BFC之余顺带也讲一下IFC呢?呃。。。这个。。呵呵。。。实际的情况是大多数人对于IFC是不甚了了的,既然不甚了了,当然也就没什么好讲的。实际上IFC确实非常简单,但不代表它不重要,它和BFC平起平坐,分别主宰了页面的高度和宽度。相对于BFC,IFC的布局属性非常简单,只有一个vertical-align,但是如果你经常写CSS,就会发现这个属性非常重要。比如我们如果要实现一个元素在父容器中垂直居中就必须用到它。所有的垂直对齐都与IFC相关,都与vertical-aline相关。那么在此之前,你是否知道vertical-align是一个IFC属性呢?那么IFC究竟是什么呢?IFC全名为inline formmat context,译为行内格式化上下文。如果你想更加形象地了解它,你不妨到楼下小卖部花五毛钱买一本小学生用的英文练习本。英文练习本的行框格式就非常的近似于标准的IFC格式。不同之处在于标准IFC格式在字体区间多了一条基线。所有的字符默认都是严格按照基线对齐的,你可以想象如果你在英文练习本上要写上一串在行内严格居中的单词,你是不是要在字符区间内再画一条中线,并以中线为参照书写单词呢?基线的意义就在于此。有趣的是,IFC基线并不是严格居中,或许是出于美观,严格的行内居中的文本并不好看。不信自己不妨写写看。关于基线的准确位置,我们可以在键盘上输入一个小写x,基线紧贴着x的底边穿过,x在IFC中非常特殊,它代表了一个标准量,由此产生了一个新的高度单位ex,表示的一个x字母的高度。现在比较流行用等高字体,大写小写都一样高,这个单位没什么用,在非等高字体中,这个单位用的比较多。目前也还支持用ex。关于IFC,我们需要知道它的两个区间,和6条对齐线。两个区间分别是行高区间,字体区间。设置行高区间的属性是line-height,设置字体区间的属性是font-size。6条对齐线分别是top,middle,bottom,text-top,text-bottom,base-line,可以在vertical-align中进行设置。关于这些属性值用法的细节,你不妨参阅相关资料(如MDN),里面有更为详尽的说明。关于IFC和BFC的关系你不妨讲它理解为英文练习本中页与行的关系,一个负责行,一个负责页,页里面包含行。没有行就没有内容,没有页那就什么都没有。关于IFC,你已经有了一个比较基本的认识,那么接下来,我们将讨论一下IFC具体的表现特征。首先需要搞清的是什么类型的元素是IFC元素,简单来说,所有的inline类型的元素都是IFC元素,当然还有一些别的元素,不一一列举,总之判断是否是IFC元素的终极标准是:该元素是否能和普通文本同行显示。比较特殊的是float元素,它可以和多行一起显示,不过它的对齐方式仍然是和单行对齐。就好比说一般人都只会找一个女朋友,但float会同时交往很多个女朋友,并且他还喜欢邀请她们一起玩,大家一起还玩的很嗨,而float的真爱是第一个女朋友,他只和她对齐。IFC元素一旦在页面中出现就意味着内容输出,那么即使元素里面没有任何内容,它也会产生一个空白的输入节点,这个空白节点就好比是我们平时进行打字的输入光标,但是它不是真实可见的。我们通过以下代码可以观察到它的存在:span{font-size:30px, border:3px solid red}。我们在body插入一个空白的span标签,这时我们看到了一个高度为30px的红色竖条。只要我们标签的内容不为空,它就消失了。该空白节点在布局上引起了很多的误会,最为人熟知的便是图片的border问题,我们发现图片border底边始终与图片底边之间有空白存在。当我们把font-size设置为0时,空白就消失了。道理很简单,你打字时将字体大小调到0,那么输入光标也会消失。很多人不知道该空白节点的由来,因此比较通俗的说法称之为幽灵空白节点。这个节点有时候会造成非常麻烦的布局问题,因为它不可见,无法观测,你只能猜测是否是它导致了布局异常。幽灵空白节点从IFC诞生之日开始就一直存在,那么这是不是一种设计上的缺陷呢?实际上人们对于幽灵空白节点存在着巨大的误解,相比于它在内容排版中所起的作用,它所造成的困扰根本不算什么,更何况很多困扰的根本原因是因为人们对它的认识不够充分,甚至连它怎么产生的都不知道,那么在对它有了基本的了解后,很多因它而起的问题就都可以避免了。我们前文所谈到的浮动锚点本质上就是一个幽灵空白节点。浮动元素会在外层插入一个幽灵空白节点,别看只是一个空白节点,它的IFC属性却是一应俱全,浮动元素的block盒会和外层的幽灵空白节点进行垂直对齐,默认是顶部对齐,幽灵空白节点又会与相邻的IFC元素对齐。这样,我们的图文就能并排显示了。inline-block元素和float元素实现原理一样,区别是inline-block的默认对齐方式是基线对齐,border底边和图片之间多出来的间隙正好就是基线到IFC的bottom的距离。因此,我们如果要消除这个空白区域,除了设置font-size=0之外还可以设置vertical-align=bottom。关于vertical-align属性的用法可以参阅相关文档(如MDN)。幽灵空白节点和vertical-align是IFC布局的核心所在,没有它们,垂直对齐将无法实现,元素将无法通过自身实现垂直居中了,float也找不到多个女朋友了,不光是float,所有元素都找不到女朋友了,多么令人悲伤!最后补上一段刚才讲到的图片border问题的示例,希望你好好把玩,并从中得到收获。
style:
img{
border:1px solid red;
}
body:
<img src='./logo.png'/>
总的来说,IFC在布局方面还是非常简单的,因为关于布局的属性就一个vertical-align,属性值也只有规定的6种。IFC的绝大部分属性都是关于文本处理的。IFC布局简单的特性并非由它自身决定,而是由浏览器视口特征决定的。还是以英语练习本为例,你在练习本上书写单词是不是很少考虑垂直方向的空间呢?是的,你只需要写完一行继续写下一行就行了,因为永远都有下一行,除非本子用完了。倒是水平方向上的行距需要慎重考虑,因为水平方向的空间非常有限,比如你想在页面上画一张表格,你一般都只需要思考如何分配水平方向的列的宽度,垂直方向基本不用考虑,因为不管你有多少行,垂直方向总是有空间的。网页同样如此,用户在浏览网页时,在垂直方向上滚动鼠标以查看内容是很正常的,但几乎没有网页会让用户横向滚动鼠标来查看更多内容,虽然可以这么做,但没有用户在浏览网页时希望得到这样的用户体验。我们日常的自适应和响应式布局,都是针对水平方向的,垂直方向什么都不要做,只需要像叠砖块一样一层一层叠就好了。因此,我们对垂直方向不需要有太多的控制,因为它空间无限。但这并不代表垂直方向完全不需要考虑,甚至于对于垂直方向的布局知识完全无知。作为一名工程师,如果你只从水平方向考虑问题,那么你可能认为自己的工作就是砌砖,如果你能从水平和垂直方向去考虑,你会认为自己在建造一座伟大的建筑。做同样的事,格局和眼光不一样,产生的效果也不一样。最后,心中有整座大厦的你成为了伟大的建筑工程师,而那个只看到眼前那堆砖块磨洋工的你仍然默默无闻于人海之中。你是这么认为的吗?关于IFC,就是这些,希望你有所收获。
clear
最后补充一点关于clear元素的细节。这个元素的字面意思容易给人造成一些错误的印象。在一般的编程语言中,该名字的函数通常用于清理数据,比如清除掉列表或数组里的数据项。人们可能很自然地就认为css中地clear也有做一些清理方面工作。实际上它什么清理工作也没做,它做的实际工作是把元素的margin-top设置为浮动元素的高度,本质上它就是把浮动造成的高度塌陷再顶回来。我们完全可以通过设置margin-top达到同样的效果。这个需要和其他编程语言的惯常用法区别开来,以免造成一些误会。好了,就到这里吧,关于浮动,您还知道哪些不为人知的细节呢?欢迎给我留言,一起分享吧!那么,下一篇文章见。。。