3.3 浮动与清除

浮动和清除是用来组织页面布局的又一柄利剑,这柄剑的剑刃就是floatclear属性。浮动,你看这俩字儿多形象,意思就是把元素从常规文档流中拿出来。拿出来干什么?一是可以实现传统出版物上那种文字绕排图片的效果,二是可以让原来上下堆叠的块级元素,变成左右并列,从而实现布局中的分栏。

浮动元素脱离了常规文档流之后,原来紧跟其后的元素就会在空间允许的情况下,向上提升到与浮动元素平起平坐。

如果浮动元素后面有两个段落,而你只想让第一段与浮动元素并列(就算旁边还能放下第二段,也不想让它上来),怎么办?用clear属性来“清除”第二段,然后它就乖乖地呆在浮动元素下面了。

影响浮动的因素还有很多,推荐读者看一看Eric Meyer的那本Cascading Style Sheets 2.0 Programmer's Reference(2006,McGraw-Hill Osborne Media)。这里摘录Eric在那本书里写的一句话:“当你浮动一个元素的时候……这些(浮动)规则就好像在说:‘尽量把这个元素往上放,能放多高放多高,直到碰到某个元素的边界为止。’”即使这本书的出版年份是2006年,但它仍然是任何CSS高手必备的一本参考书。这本书涵盖了CSS运行机制的方方面面,其中很多在别的书里是找不到的。

在详细介绍这两个属性之前,有必要先给读者提个醒。虽然浮动元素是必要的也是非常有用的CSS技术,但浮动之后可能带来各种令CSS初学者困惑的问题。毕竟,浮动的内容已经脱离了文档流,因而无论原先在标记中包含它还是跟随它的元素,其布局都会受到它的影响。不过不要紧,本节后面展示浮动与清除的例子都经过了仔细推敲,我希望通过这些例子能让你真正理解并掌握浮动技术。

3.3.1 浮动

CSS设计float属性的主要目的,是为了实现文本绕排图片的效果。然而,这个属性居然也成了创建多栏布局最简单的方式。

CSS3 Multi-column Layout Module规定了如何用CSS定义多栏布局。但在本书写作时,只有Opera和IE10支持相应属性。因此在可以预见的未来,float属性仍然是创建多栏布局的最佳途径。

那就让我们从实现文本绕排图片的效果开始吧。

1. 文本绕排图片

为了实现文本绕排图片的浮动效果,必须在标记中先写图片,然后再写环绕它的文本。

  1. <img …… />
  2. <p>…the paragraph text…</p>

CSS规则如下。

  1. /*为简明起见,省略了字体声明*/
  2. p {margin:0; border:1px solid red;}
  3. /*外边距防止图片紧挨文本*/
  4. img {float:left; margin:0 4px 4px 0;}

以上规则会让图片浮动到左侧,从而让文本绕排到右侧,如图3-16所示。

enter image description here图3-16 浮动图片会从文档流中被移除,如果在标记中有文本元素跟在它后面,则其中的文本会绕开图片

说得形象一点,在你浮动一张图片或者其他元素时,你是在要求浏览器把它往上方推,直到它碰到父元素(也就是body元素)的内边界。后面的段落(带灰色边框)不再认为浮动元素在文档流中位于它的前面了,因而它会占据父元素左上角的位置。不过,它的内容(文本)会绕开浮动的图片。

浮动非图片元素时,必须给它设定宽度,否则后果难以预料。图片无所谓,因为它本身有默认的宽度。

2. 创建分栏

在此基础上创建多栏,只要再用一次float属性,就这么简单。如图3-17所示,只要给段落设定宽度,然后也浮动它即可。

  1. p {float:left; margin:0; width:200px; border:1px solid red;}
  2. img {float:left; margin:0 4px 4px 0;}

enter image description here图3-17 浮动图片旁边的固定宽度段落一经浮动,就会变成布局中的一栏,其文本也不会再绕排图片了

你这样同时浮动图片和“有宽度的”段落,会导致段落的文本绕排效果消失,而浮动的段落也会尽可能向左向上移动。就这样,这个段落就构成了紧挨着图片的一栏。这就是使用float属性创建多栏布局的原理。换句话说,如果几个相邻的元素都具有设定的宽度,都是浮动的,而且水平空间也足以容纳它们,它们就会并列排在一行。

如果你创建了三个浮动、固定宽度的元素,它们就会像这样并排在一行,构成三栏布局的框架。每个元素都可以作为容器,包含其他元素。关于浮动布局的内容,我们将在第5章再详细讨论。

接下来我们再看看浮动的另一面,这也是必须得理解的。浮动元素位于“文档流外部”,因而它已经不被包含在标记中的父元素之内了。正因为如此,它对布局可能产生破坏性影响。

3.3.2 围住浮动元素的三种方法

浮动元素脱离了文档流,其父元素也看不到它了,因而也不会包围它。这种情况有时候并非我们想要的,本节向大家传授三种围住浮动子元素的方法。记住,这三种方法你都得掌握,这样才能审时度势,选择最合适的一种。

为了演示浮动元素的行为,这种行为对布局会产生什么影响,以及解决这个问题的三种方法,我们首先要从一张带标题的图片开始。图片和标签包含在一个section元素中,而section元素后面跟着一个footer元素。可以把这个footer元素想象成很多网页底部都会有的与页面同宽的页脚。

  1. <section>
  2. <img src="images/rubber_duck2.jpg">
  3. <p>It’s fun to float.</p>
  4. </section>
  5. <footer> Here is the footer element that runs across the bottom of the page.</footer>

这样,你才会知道究竟会发生什么。图3-18展示了应用以下规则后的sectionfooter的元素盒子。

  1. section {border:1px solid blue; margin:0 0 10px 0;}
  2. /*删除默认的上下外边距*/
  3. p {margin 0;}
  4. /*为简明起见,省略了字体声明*/
  5. footer {border:1px solid red;}

enter image description here图3-18 可以看到页面中的两个块级元素sectionfooter,前者包含一张图片及标题,后者在常规文档流中位于前者下方

现在我们看到的是常规文档流,即块级元素包围着所有子元素,而且在页面中自上而下相互堆叠在一起。假设我们想让图片标题位于图片右侧,而不是像现在这样位于下方。运用刚刚学到的知识,我们知道实现这个目标最简单的方式就是浮动图片。试试看吧。

  1. section {border:1px solid blue; margin:0 0 10px 0;}
  2. img {float:left;}
  3. footer {border:1px solid red;}

图3-19展示了结果。

enter image description here图3-19 浮动图片后标题跑到了右边,但父元素section也收缩到只包含文本的高度高度

妈呀!标题倒是跑到右边了,可section也不再包围浮动元素了,它只包围非浮动的元素。于是,footer被提了上来,紧挨着前一个块级元素——section。这样是没错儿,可结果呢,不是我们想要的。

方法一:为父元素添加overflow:hidden

第一个方法很简单,缺点是不太直观,即为父元素应用overflow:hidden,以强制它包围浮动元素。

  1. section {border:1px solid blue; margin:0 0 10px 0; overflow:hidden;}
  2. img {float:left;}
  3. p {border:1px solid red;}

overflow:hidden声明应用到容器元素后,footer又回到了我们期望的位置,如图3-20所示。

enter image description here图3-20 给容器元素应用overflow:hidden声明后,它又包围了浮动元素

实际上,overflow:hidden声明的真正用途是防止包含元素被超大内容撑大。应用overflow:hidden之后,包含元素依然保持其设定的宽度,而超大的子内容则会被容器剪切掉。除此之外,overflow:hidden还有另一个作用,即它能可靠地迫使父元素包含其浮动的子元素。

方法二:同时浮动父元素

第二种促使父元素包围其浮动子元素的方法,是也让父元素浮动起来。

  1. section {border:1px solid blue; float:left; width:100%;}
  2. img {float:left;}
  3. footer {border:1px solid red; clear:left;}

浮动section以后,不管其子元素是否浮动,它都会紧紧地包围(也称收缩包裹)住它的子元素。因此,需要用width:100%再让section与浏览器容器同宽。另外,由于section现在也浮动了,所以footer会努力往上挤到它旁边去。为了强制footer依然呆在section下方,要给它应用clear:left。被清除的元素不会被提升到浮动元素的旁边。以上代码能得到与图3-20相同的效果。

方法三:添加非浮动的清除元素

第三种强制父元素包含其浮动子元素的方法,就是给父元素的最后添加一个非浮动的子元素,然后清除该子元素。由于包含元素一定会包围非浮动的子元素,而且清除会让这个子元素位于(清除一侧)浮动元素的下方,因此包含元素一定会包含这个子元素——以及前面的浮动元素。在包含元素最后添加子元素作为清除元素的方式有两种。

第一种方式不太理想,也就是简单地在HTML标记中添加一个子元素,并给它应用clear属性。由于没有默认的样式,不会引入多余空间,div元素很适合这个目的。

  1. <section>
  2. <img src="images/rubber_duck.jpg">
  3. <p>It's fun to float.</p>
  4. <div class="clear_me"></div>
  5. </section>
  6. <footer> Here is the footer element…</footer>

在此,我为div添加了一个类,以便于在CSS中添加它。

  1. section {border:1px solid blue;}
  2. img {float:left;}
  3. .clear_me {clear:left;}
  4. footer {border:1px solid red;}

这样,浮动的元素被父元素包围住了,结果如图3-20所示。如果你特别不想添加这个纯表现性元素,我再告诉你一个用CSS来添加这个清除元素的方法。首先,要给section添加一个类。

  1. <section class="clearfix">
  2. <img src="images/rubber_duck.jpg">
  3. <p>It's fun to float.</p>
  4. </section>
  5. <footer> Here is the footer element…</footer>

然后,再使用这个神奇的clearfix规则!

  1. .clearfix:after {
  2. content:".";
  3. display:block;
  4. height:0;
  5. visibility:hidden;
  6. clear:both;
  7. }

这个clearfix规则最早是由程序员Tony Aslett发明的,它只添加了一个清除的包含句点作为非浮动元素(必须得有内容,而句点是最小的内容)1。规则中的其他声明是为了确保这个伪元素没有高度,而且在页面上不可见。

1 :after会在元素内容——而不是元素后面插入一个伪元素。——译者注

使用clear:both意味着section中新增的子元素会清除左、右浮动元素(位于左、右浮动元素下方)。这里当然可以只用left,但both也适用于将来图片float:right的情况。

同样,浮动的元素又像图3-20所示的一样被包围住了。但这次标记里没有额外硬编码的元素。好奇的话,你可以临时把clearfix规则中的heightvisibility声明删除,看一看通过伪元素添加到标记中的句点。

我在自己写的所有网站中都使用clearfix规则来解决浮动问题,因为浮动是实现多栏布局(在更多浏览器支持CSS3的Multi-column Layout Module之前)唯一最可靠的方式。这一点第5章会跟大家详细解释。

好了,该回过头来作个总结了。要想强迫父元素包围其浮动的子元素有三种方式,哪三种?

  • 为父元素应用overflow:hidden
  • 浮动父元素
  • 在父元素内容的末尾添加非浮动元素,可以直接在标记中加,也可以通过给父元素添加clearfix类来加(当然,样式表中得需要相应的clearfix规则)这三种方法的使用要因地制宜。比如,不能在下拉菜单的顶级元素上应用overflow:hidden,否则作为其子元素的下拉菜单就不会显示了。因为下拉菜单会显示在其父元素区域的外部,而这恰恰是overflow:hidden所要阻止的。再比如,不能对已经靠自动外边距居中的元素使用“浮动父元素”技术,否则它就不会再居中,而是根据浮动值浮动到左边或右边了。总之,掌握了这三种技术之后,再碰到需要包围浮动元素的情况,你就能够游刃有余了。

没有父元素时如何清除

有时候,在清除某些浮动元素时,不一定正好有那么个父元素可以作为容器来强行包围它们。此时,最简单的办法就是给一个浮动元素应用clear:both,强迫它定位在前一个浮动元素下方。然而,在空间足以容纳多个元素向上浮动时,这个简单的办法未必奏效,我们还得另辟蹊径。

为了演示这种情况,图3-21展示了一个页面,其中包含6个元素:3张图片和介绍它们的3个文本段落。这个页面布局是通过浮动图片实现的,因此在标记中跟在图片后面的文本会向上走,停靠在浮动图片的右侧。

enter image description here图3-21 由于第二段文字下方有空间,所以第三张图片及说明文字会上浮到第二张图片右侧,这不是我们想要的结果

以下是图3-21中页面对应的HTML(为节省版面,删除了部分文字):

  1. <section>
  2. <img src="images/rubber_duck3.jpg">
  3. <p>This text sits next to the image and because the…</p>
  4. <img src="images/beach_ball.jpg">
  5. <p>This text is short, so the next image can float up…</p>
  6. <img src="images/yellow_float.jpg">
  7. <p>Because the previous image’s text does not…</p>
  8. </section>

相应的CSS如下:

  1. section {width:300px; border:1px solid red;}
  2. img {float:left; margin:0 4px 4px 0;}
  3. /*为简明起见,省略了字体声明*/
  4. p {margin:0 0 5px 0;}

我们的目标是让每段文字停靠在相应的图片旁边。然而,第二段文字太短了,都没有够到第二张浮动图片的下沿。这就给下一对儿图片/段落向上浮动留出了空间。

这个例子中的布局效果从技术角度看是正确的:第三对儿图片/段落有条件(有空间)停靠在第二张浮动图片旁边,于是它们就毫不客气地靠了过去。毕竟,浮动元素的使命不就是尽可能地向左上或右上迁移吗。可这个视觉上的结果却不是我们想要的。

由于每一对儿图片/段落都没有包含元素,在此就无法使用前面讨论的“强制父元素包围”的战术。不过,我照样可以使用clearfix规则呀!

  1. .clearfix:after {
  2. content:".";
  3. display:block;
  4. height:0;
  5. visibility:hidden;
  6. clear:both;
  7. }

像这样给每个段落都加上clearfix类:

  1. <section>
  2. <img src="images/rubber_duck3.jpg">
  3. <p class="clearfix">This text sits next to the image and because the…</p>
  4. <img src="images/beach_ball.jpg">
  5. <p class="clearfix">This text is short, so the next image can float up…</p>
  6. <img src="images/yellow_float.jpg">
  7. <p class="clearfix">Because the previous image’s text does not…</p>.
  8. </section>

如图3-22所示,在每个段落内容的最后添加了“清除子元素”,我们想要的布局效果实现了。因为第三对儿图片和段落前面增加了一个清除元素,所以它们就不能再往上走了。注意,我没有只给第二个段落添加clearfix类,而是每个段落都加上了一个。如果是真正的网站开发,就得这么做啊。这样,无论将来哪个段落的文本高度低于图片了,页面布局都不会被破坏。

enter image description here图3-22 通过clearfix类添加了清除元素后,布局就跟我们期望的一样了

说到这儿,相信读者对floatclear属性都有了深入理解了。本章最后,再给大家介绍两个对CSS布局至关重要的属性:positiondisplay