浮动
文档流: 文档流是文档中可显示对象在排列时所占用的位置。
浮动的定义: 使元素脱离文档流,按照指定方向发生移动,遇到父级边界或者相邻的浮动元素停了下来。
浮动的实际用途,可设置文字环绕或使元素宽度由内容填充(类似inline-block
)。使用浮动需要注意的是如果浮动的元素高度比父级容器还高,那么需要设置父级容器的overflow
属性为auto
,使其自动撑满。
清除浮动还是闭合浮动(Enclosing float or Clearing float)
很多人都已经习惯称之为清除浮动,以前我也一直这么叫着,但是确切地来说是不准确的。我们应该用严谨的态度来对待代码,也能更好地帮助我们理解开头的三个问题。
1)清除浮动: 清除对应的单词是clear
,对应CSS中的属性是clear: left | right | both | none
;
2)闭合浮动: 更确切的含义是使浮动元素闭合,从而减少浮动带来的影响。
通过以上实例发现,其实我们想要达到的效果更确切地说是闭合浮动,而不是单纯的清除浮动,在footer上设置clear: both
清除浮动并不能解决wrap
高度塌陷的问题。
结论: 用闭合浮动比清除浮动更加严谨,所以后文中统一称之为: 闭合浮动。
参见DEMO: 高度塌陷
为何要闭合浮动?
要解答这个问题,我们得先说说CSS中的定位机制: 普通流,浮动,绝对定位 (其中position:fixed
是position:absolute
的一个子类)。
1)普通流: 很多人或者文章称之为文档流或者普通文档流,其实标准里根本就没有这个词。如果把文档流直译为英文就是document flow,但标准里只有另一个词,叫做普通流(normal flow),或者称之为常规流。但似乎大家更习惯文档流的称呼,因为很多中文翻译的书就是这么来的。比如《CSS Mastery》,英文原书中至始至终都只有普通流normal flow(普通流) 这一词,从来没出现过document flow (文档流)
2)浮动: 浮动的框可以左右移动,直至它的外边缘遇到包含框或者另一个浮动框的边缘。浮动框不属于文档中的普通流,当一个元素浮动之后,不会影响到块级框的布局而只会影响内联框(通常是文本)的排列,文档中的普通流就会表现得和浮动框不存在一样。当浮动框高度超出包含框的时候,也就会出现包含框不会自动伸高来闭合浮动元素(“高度塌陷”现象)。顾名思义,就是漂浮于普通流之上,像浮云一样,但是只能左右浮动。
正是因为浮动的这种特性,导致本属于普通流中的元素浮动之后,包含框内部由于不存在其他普通流元素了,也就表现出高度为0(高度塌陷)。在实际布局中,往往这并不是我们所希望的,所以需要闭合浮动元素,使其包含框表现出正常的高度。
闭合浮动的原理——了解hasLayout和Block formatting contexts
清除浮动的方式
1)添加额外标签
这是在学校老师就告诉我们的 一种方法,通过在浮动元素末尾添加一个空的标签例如<div style=”clear:both”></div>
,其他标签<br>
等亦可。
1 | <div class="wrap" id="float1"> |
参见DEMO: 清除浮动clear:both–空div
优点: 通俗易懂,容易掌握
缺点: 可以想象通过此方法,会添加多少无意义的空标签,有违结构与表现的分离,在后期维护中将是噩梦,这是坚决不能忍受的,所以你看了这篇文章之后还是建议不要用了吧。
2)使用br
标签和其自身的html属性
这个方法有些小众,br
有clear=“all | left | right | none”
属性
1 | <div class="wrap" id="float2"> |
参见DEMO: 清除浮动clear:both–br
优点: 比空标签方式语义稍强,代码量较少
缺点: 同样有违结构与表现的分离,不推荐使用
3) 父元素设置overflow:hidden
通过设置父元素overflow
值设置为hidden
;在IE6中还需要触发hasLayout
,例如zoom: 1
;
1 | <div class="wrap" id="float3" style="overflow:hidden; *zoom:1;"> |
参见DEMO:清除浮动–父元素设置overflow:hidden
优点: 不存在结构和语义化问题,代码量极少
缺点: 内容增多时候容易造成不会自动换行导致内容被隐藏掉,无法显示需要溢出的元素;04年POPO就发现overflow:hidden
会导致中键失效,所以还是不要使用了。
4) 父元素设置overflow:auto
属性
同样IE6需要触发hasLayout,演示和3差不多
参见DEMO:清除浮动–父元素设置overflow:auto
优点: 不存在结构和语义化问题,代码量极少
缺点: 多个嵌套后,firefox某些情况会造成内容全选;IE中mouseover
造成宽度改变时会出现最外层模块有滚动条等,firefox早期版本会无故产生focus
等,不要使用
5)父元素也设置浮动
优点: 不存在结构和语义化问题,代码量极少
缺点: 使得与父元素相邻的元素的布局会受到影响,不可能一直浮动到body
,不推荐使用
6)父元素设置display:table
参见DEMO:清除浮动–父元素设置display:table
优点: 结构语义化完全正确,代码量极少
缺点: 盒模型属性已经改变,由此造成的一系列问题,得不偿失,不推荐使用
7)使用::after
伪元素
需要注意的是::after
是伪元素(Pseudo-Element),不是伪类(某些CSS手册里面称之为“伪对象”),很多闭合浮动大全之类的文章都称之为伪类,不过csser要严谨一点,这是一种态度。
由于IE6-7不支持::after
,使用zoom:1
触发hasLayout。
该方法源自于:How To Clear Floats Without Structural Markup
原文全部代码如下:
1 | <style type="text/css"> |
鉴于 IE/Mac的市场占有率极低,我们直接忽略掉,最后精简的代码如下:
1 | <style type="text/css"> |
清除浮动方式总结
通过对比,我们不难发现,其实以上列举的方法,无非有两类:
利用clear
属性清除浮动
其一,通过在浮动元素的末尾添加一个空元素,设置clear: both
属性,::after
伪元素其实也是通过content在元素的后面生成了内容为一个点的块级元素;
当clear
应用于非浮动元素时,它将非浮动元素的边框边界移动到所有相关浮动元素外边界的下方。这个行为作用时会导致margin collapsing不起作用。
当clear
应用于浮动元素时,它将元素的外边界移动到所有相关的浮动元素外边界的下方。这会影响后面浮动元素的布局,后面的浮动元素的位置无法高于它之前的元素。
要被清除的相关浮动元素指在相同BFC中的前置浮动。
使父容器形成BFC
其二,通过设置父元素overflow
或者display: table
属性来闭合浮动
清除浮动/闭合浮动原理
在CSS2.1里面有一个很重要的概念,但是国内的技术博客介绍到的比较少,那就是Block formatting contexts (块级格式化上下文),以下简称 BFC。
CSS3里面对这个规范做了改动,称之为: flow root,并且对触发条件进行了进一步说明。
BFC特性
1) BFC会阻止垂直外边距叠加
当两个相邻的块框在同一个BFC中时,只要他们之间没有阻挡(边框,非空内容,padding等),它们之间垂直方向的外边距会发生叠加。
换句话说,如果这两个相邻的块框不属于同一个BFC,那么它们的外边距就不会叠加。对于相邻两个元素意义不大,没有必要给他们加一个外壳。但是对于嵌套元素来说就很有必要了,只要把父元素设置为BFC就可以了。这样子元素的margin
就不会和父元素的margin
发生重叠了。
2) BFC不会重叠浮动元素
根据规定,一个BFC的边框不能和它里面的元素的外边距重叠。这就意味着浏览器将会给块级格式化上下文创建隐式的外边距来阻止它和浮动元素的外边距叠加。由于这个原因,当给一个挨着浮动的块级格式化上下文添加负的外边距时将会不起作用(Webkit和IE6在这点上有一个问题——可以看这个测试用例)。
3) BFC可以包含浮动
利用这三条特性我们可以用来闭合浮动,也就是说只要父元素形成BFC就可以。
如何触发BFC
- 根元素或其它包含它的元素
- 浮动元素 (元素的
float
不是none
) - 绝对定位元素 (元素具有
position
为absolute
或fixed
) - 内联块 (元素具有
display: inline-block
) - 表格单元格 (元素具有
display: table-cell
,HTML表格单元格默认属性) - 表格标题 (元素具有
display: table-caption
, HTML表格标题默认属性) - 具有
overflow
且值不是visible
的块元素(hidden
,auto
,scroll
) display: flow-root
column-span: all
应当总是会创建一个新的格式化上下文,即便具有column-span: all
的元素并不被包裹在一个多列容器中。fieldset
元素
display:table
本身并不会创建BFC,但是它会产生匿名框(anonymous boxes),而匿名框中的display:table-cell
可以创建新的BFC,换句话说,触发块级格式化上下文的是匿名框,而不是display:table
。所以通过display:table
和display:table-cell
创建的BFC效果是不一样的。fieldset
元素在www.w3.org里目前没有任何有关这个触发行为的信息,直到HTML5标准里才出现。有些浏览器bugs(Webkit,Mozilla)提到过这个触发行为,但是没有任何官方声明。实际上,即使`fieldset`在大多数的浏览器上都能创建新的块级格式化上下文,开发者也不应该把这当做是理所当然的。CSS 2.1没有定义哪种属性适用于表单控件,也没有定义如何使用CSS来给它们添加样式。用户代理可能会给这些属性应用CSS属性,建议开发者们把这种支持当做实验性质的,更高版本的CSS可能会进一步规范这个。
通俗地来说: 创建了BFC的元素就是一个独立的盒子,里面的子元素不会在布局上影响外面的元素,反之亦然,同时BFC仍然属于文档中的普通流。
至此,您或许明白了为什么overflow:hidden
或者auto
可以闭合浮动了,真是因为父元素创建了新的BFC。
hasLayout
从表现上来说,hasLayout
可以等同于 BFC。
IE6-7的显示引擎使用的是一个称为布局(layout)的内部概念,由于这个显示引擎自身存在很多的缺陷,直接导致了IE6-7的很多显示bug。当我们说一个元素“得到 layout”,或者说一个元素“拥有 layout” 的时候,我们的意思是指它的微软专有属性[hasLayout]
为此被设为了true
。IE6-7使用布局的概念来控制元素的尺寸和定位,那些拥有布局(have layout)的元素负责本身及其子元素的尺寸设置和定位。如果一个元素的hasLayout
为false
,那么它的尺寸和位置由最近拥有布局的祖先元素控制。
触发hasLayout的条件
position: absolute
float: left|right
display: inline-block
width
: 除auto
外的任意值height
: 除auto
外的任意值(例如很多人闭合浮动会用到 height: 1% )zoom
: 除normal
外的任意值writing-mode
: tb-rl
在 IE7 中,overflow
也变成了一个 layout 触发器:
overflow: hidden|scroll|auto
( 这个属性在IE之前版本中没有触发 layout 的功能。 )overflow-x|-y: hidden|scroll|auto
(CSS3 盒模型中的属性,尚未得到浏览器的广泛支持。他们在之前IE版本中同样没有触发 layout 的功能)
总结
在支持BFC的浏览器(IE8+,firefox,chrome,safari)通过创建新的BFC闭合浮动;
在不支持BFC的浏览器 (IE6-7),通过触发hasLayout闭合浮动。
闭合浮动改进方法
上面已经列举了7种闭合浮动的方法,通过第三节分析的原理,我们发现其实更多的: display: table-cell
,display: inline-block
等只要触发了BFC的属性值都可以闭合浮动。从各个方面比较,::after
伪元素闭合浮动无疑是相对比较好的解决方案了,下面详细说说该方法。
伪元素和伪类
伪类的操作对象是文档树中已有的元素,而伪元素则创建了一个文档数外的元素。因此,伪类与伪元素的区别在于:有没有创建一个文档树之外的元素。
伪元素使用::
,伪类使用:
。
实现过程
1 | .clearfix::after {content:"."; display:block; height:0; visibility:hidden; clear:both; } |
1) display:block
使生成的元素以块级元素显示,占满剩余空间;
2) height:0
避免生成内容破坏原有布局的高度。
3) visibility:hidden
使生成的内容不可见,并允许可能被生成内容盖住的内容可以进行点击和交互;
4)通过content:"."
生成内容作为最后一个元素,至于content里面是点还是其他都是可以的,例如oocss里面就有经典的content:"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
,有些版本可能content
里面内容为空,一丝冰凉是不推荐这样做的,firefox直到7.0content:""
仍然会产生额外的空隙;
5)zoom: 1
触发IE hasLayout。
通过分析发现,除了clear: both
用来闭合浮动的,其他代码无非都是为了隐藏掉content生成的内容,这也就是其他版本的闭合浮动为什么会有font-size:0
,line-height:0
。
精益求精方案
由Nicolas Gallagher 大湿提出来的,原文:A new micro clearfix hack,该方法也不存在firefox中空隙的问题。
1 | /* For modern browsers */ |
参见清除浮动–父元素设置::after ::before伪元素
需要注意的是:
上面的方法用到了::before
伪元素,很多人对这个有些迷惑,到底我什么时候需要用before
呢?为什么方案一没有呢?其实它是用来处理margin
边距重叠的,由于内部元素float
创建了BFC,导致内部元素的margin-top
和上一个盒子的margin-bottom
发生叠加。如果这不是你所希望的,那么就可以加上before
,如果只是单纯的闭合浮动,after
就够了!