Skip to content

浮动及其形状

很长一段时间内,浮动元素是所有 Web 布局方案的基础(很大程度上依赖 clear 属性)。但浮动并不是为布局而生的。
与使用表格布局基本一样,把浮动作为布局工具算是无奈之举,那时别无他选。

然而,浮动自身确实相当有趣和有用的,尤其是最近新增浮动形状之后,内容可以沿非矩阵边缘浮动了。

浮动设计的初衷

虽然最初创造浮动并不是为了用于页面布局,但它在布局方面表现得很出色。然而为了理解浮动,我们首先必须牢记它的设计初衷。

浮动能将一个元素(通常是一张图片)拉到其容器的一侧,这样文档流就能够包围它(如图所示)。
这种布局在报纸和杂志中很常见,因此CSS增加了浮动来实现这种效果。

文本行包围了浮动元素

上图中,一个元素被拉到了左侧,它也可以浮动到右侧。浮动元素会被移出正常文档流,并被拉到容器边缘。
文档流会重新排列,但是它会包围浮动元素此刻所占据的空间。如果让多个元素向同侧浮动,它们就会挨着排列,如图所示。

两个浮动元素挨着排列

如果你写CSS已经有一段时间了,应该不会对这种行为感到陌生。重点是,尽管这才是浮动的设计初衷,我们却并不总是这样使用它。

在CSS早期,开发人员发现使用简单的浮动就可以移动页面的各个部分,从而实现各种各样的布局。
浮动本身不是为了实现页面布局而设计的,但是在近20年的时间里,我们把它当成了布局工具。

之所以这样做是因为它是那个年代唯一的选择。
后来,display: inline-blockdisplay: table 的问世才让我们有了别的方案,尽管二者可替代的场景有限。
Flexbox网格布局 最近几年才出现,在它们出现之前,浮动一直承担着页面布局的重任。
让我们来看看它是如何工作的。举个例子,你需要构建一个如图所示的网页。

基于浮动布局的网页

在下面的例子里,你需要用浮动来定位四个灰色的盒子,并且在盒子里面将图片浮动到文字的一侧。
现在创建一个空白的页面,并给它链接一个新的样式表,然后将代码添加到页面中。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
<!doctype html>
<head>
  <style>
    :root {
      box-sizing: border-box;
    }

    *,
    ::before,
    ::after {
      box-sizing: inherit;
    }

    body {
      background-color: #eee;
      font-family: Helvetica, Arial, sans-serif;
    }

    body * + * {
      margin-top: 1.5em;
    }

    header {
      padding: 1em 1.5em;
      color: #fff;
      background-color: #0072b0;
      border-radius: .5em;
      margin-bottom: 1.5em;
    }

    .main {
      padding: 0 1.5em;
      background-color: #fff;
      border-radius: .5em;
    }

  </style>
</head>

<body>
  <div class="container">
    <header>
      <h1>Franklin Running Club</h1>
    </header>

    <main class="main clearfix">
      <h2>Running tips</h2>

      <div>
        <div class="media">
          <img class="media-image" src="shoes.png">
          <div class="media-body">
            <h4>Strength</h4>
            <p>
              Strength training is an important part of
              injury prevention. Focus on your core&mdash;
              especially your abs and glutes.
            </p>
          </div>
        </div>

        <div class="media">
          <img class="media-image" src="runner.png">
          <div class="media-body">
            <h4>Cadence</h4>
            <p>
              Check your stride turnover. The most efficient
              runners take about 180 steps per minute.
            </p>
          </div>
        </div>

        <div class="media">
          <img class="media-image" src="runner.png">
          <div class="media-body">
            <h4>Change it up</h4>
            <p>
              Don't run the same every time you hit the
              road. Vary your pace, and vary the distance
              of your runs.
            </p>
          </div>
        </div>

        <div class="media">
          <img class="media-image" src="shoes.png">
          <div class="media-body">
            <h4>Focus on form</h4>
            <p>
              Run tall but relaxed. Your feet should hit
              the ground beneath your hips, not out in
              front of you.
            </p>
          </div>
        </div>

      </div>
    </main>
  </div>
</body>

以上代码清单给出了页面的结构:一个头部和一个包含网页主要内容的主元素。主
元素内是网页标题,紧跟着一个匿名的div(即没有类或ID的div)。
这有助于对四个灰色的媒体元素进行分组,每个媒体元素包含一个图片和一个正文元素。

提示
通常,最简单的方式是先将网页的大块区域布局好,再逐级布局内部的小元素。

基础样式中,包括前面提到的一个全局设定的box-sizing和猫头鹰选择器。
接下来需要设置页面内容的宽度,如图所示。注意观察页面两侧相等的浅灰色页边距,以及头部和主容器里等宽对齐的内容。

限定宽度的页面

这种布局常用于将网页内容居中。
通过将内容放置到两个嵌套的容器中,然后给内层的容器设置外边距,让它在外层容器中居中(如图所示)。
Web开发人员Brad Westfall把这种布局方式叫作双容器模式(double container pattern)。

双容器模式

在本例中,<body> 就是外层容器。因为它默认是100%的网页宽度,所以不用给它添加新的样式。
<body> 内部,整个网页的内容放在了 <div class="container">,也就是内层容器中。
对于内层容器,需要设置一个max-width,并将外边距设置为auto,使内容居中

1
2
3
4
.container {
    max-width: 1080px;
    margin: 0 auto;
}

这里使用了 max-width 而不是 width,因此如果视口宽度小于 1080px 的话,内层容器就能缩小到 1080px 以下。
换句话说,在小视口上,内层容器会填满屏幕,在大视口上,它会扩展到 1080px。
这种方式能有效避免在小屏幕上出现水平滚动条。

是否还有必要学习浮动:

Flexbox正在迅速取代浮动在页面布局中的地位。对新手开发人员而言,Flexbox的行为很直观,可预测性更好。
你可能会问是否还有必要学习浮动,CSS浮动的时代是不是结束了?

在现代浏览器中,不用浮动也能比过去更好地实现布局,甚至可以完全弃用浮动。但是如果要支持IE浏览器,现在放弃浮动还为时过早。
只有IE10和IE11支持Flexbox,而且还有一些bug。如果不想碰到bug,或者需要支持旧版浏览器,浮动也许是更好的选择。

另外,如果你在支持旧代码库,它很可能用到了浮动布局。
为了维护旧代码,也需要了解浮动的工作原理。还有一点,浮动布局通常不需要那么多的标记,新的布局方法则需要添加额外的容器元素。
如果你写样式时不允许修改标记,浮动更能满足你的需求。

此外,要实现将图片移动到网页一侧,并且让文字围绕图片的效果,浮动仍然是唯一的方法。

容器折叠和清除浮动

过去,浮动的行为经常受到浏览器bug的干扰,特别是在IE6和IE7中。幸亏这些浏览器几乎已经淡出市场了,我们不必再担心那些bug了。
现在我们可以保证各种浏览器对浮动的处理是一致的。

但是浮动仍有一些行为会让你措手不及。这些并不是bug,而是因为浮动严格遵循了标准。
让我们来看看浮动如何工作,以及怎样调整浮动的行为来实现理想的布局。

理解容器折叠

在上述示例的基础上,将四个媒体盒子浮动到左侧,就能立刻看到容器折叠的问题

子元素浮动时容器出现折叠

白色的背景区域是怎么回事?白色背景的确出现在了页面标题(“Running tips”)后面,但是并没有向下延伸,直到包含媒体盒子。
为了观察这一现象,将下面代码添加到你的样式表中。然后我们再解释问题的原因以及修复方法。

1
2
3
4
5
6
7
.media {
  float: left;
  width: 50%;
  padding: 1.5em;
  background-color: #eee;
  border-radius: 0.5em;
}

给媒体盒子设置好浅灰色的背景后,你以为容器的白色背景会出现在媒体盒子的后面(或者周围)。
然而白色背景延伸到第一排媒体盒子的上面就结束了。这是怎么回事?

这是因为浮动元素不同于普通文档流的元素,它们的高度不会加到父元素上。这可能看起来很奇怪,但是恰好体现了浮动的设计初衷。

浮动是为了实现文字围绕浮动元素排列的效果。在段落里浮动图片时,段落的高度并不会增长到能够容纳该图片。
也就是说,如果图片比段落文字高,下一段会直接从上一段的文字下面开始,两段文字都会围绕浮动的图片排列,如图所示:

一个容器内的浮动元素会扩展到另一个容器,这样两个容器的文字就能围绕浮动元素排列(虚线高亮的部分是容器):

图4-7

在主元素里,除了页面标题,其他元素都设置了浮动,所以容器的高度只包含页面标题的高度,浮动的媒体元素则扩展到主元素的白色背景下面。
这种行为并不是我们想要的,主元素应该向下扩展到包含灰色的盒子(如图所示)。让我们一起来解决这个问题吧。

容器向下扩展包围了浮动元素

一个解决办法是使用跟浮动配套的clear属性。将一个元素放在主容器的末尾,并对它使用clear,这会让容器扩展到浮动元素下面。
将下面代码暂时添加到页面里,就能看到clear的效果。

1
2
3
4
<main class="main clearfix">
  ...    
  <div style="clear: both"></div>
</main>

clear: both声明让该元素移动到浮动元素的下面,而不是侧面。
clear的值还可以设置为left或者right,这样只会相应地清除向左或者向右浮动的元素。
因为空div本身没有浮动,所以容器就会扩展,直到包含它,因此也会包含该div上面的浮动元素。

这种方法的确能实现预期的行为,但是不雅。要在HTML里添加不必要的标记,才能实现本应该由CSS实现的效果。
因此我们要删掉上面的空div标签,用纯CSS方案来实现相同的效果。

理解清除浮动

不用额外的div标签,我们还可以用伪元素(pseudo-element)来实现。
使用 ::after 伪元素选择器,就可以快速地在 DOM 中在容器末尾添加一个元素,而不用在 HTML 里添加标记。

伪元素——一种特殊的选择器,可以选中文档的特定部分。
伪元素以双冒号(::)开头,大部分浏览器为了向后兼容也支持单冒号的形式。
最常见的伪元素是 ::before::after,用来向元素的开始或者结束位置插入内容。

下面代码所示的是解决包含浮动问题的一种常见做法,叫作清除浮动(clearfix)。
有些开发人员将其简称为cf,正好还能表示“包含浮动”(containfloat)。将代码清添加到你的样式表中。

1
2
3
4
5
6
7
8
/* 选中容器末尾的伪元素 */
.clearfix::after {
  /* 将伪元素的 display 设置为非 inline,并给定一个 content 值,以便让伪元素出现在文档中 */
  display: block;
  content: " ";
  /* 让伪元素清除容器中的所有浮动 */
  clear: both;
}

请注意,要给包含浮动的元素清除浮动,而不是给别的元素,比如浮动元素本身,或包含浮动的元素后面的兄弟元素。

警告:
这些年来,清除浮动经历了多个迭代版本,其中一些版本比其他版本更加复杂。
很多版本为了修复不同浏览器的bug而存在细微差异。
大多数的变通方法现在已经不必要了,但是上面给出的例子里还有一个变通处理:content的值包含了空格。
当然,空字符串("")也能生效,但是在旧版的Opera浏览器中有个隐藏的bug,需要添加一个空格字符才能解决。
我倾向于保留这个空格,毕竟它也不容易被用户发现。

这个清除浮动还有个一致性问题没有解决:浮动元素的外边距不会折叠到清除浮动容器的外部,非浮动元素的外边距则会正常折叠。
比如在前面的页面里,标题“Running tips”紧挨着白色的<main>元素的顶部,它的外边距在容器外面折叠了。

一些开发人员更喜欢使用清除浮动的一个修改版,它能包含所有的外边距,这样更符合预期。
使用这个修改版,能防止标题顶部的外边距在main元素的外部折叠。如图所示,在标题上面适当留白。

清除浮动的修改版包含了所有的浮动元素和外边距。标题“Running tips”顶部的外边距现在包含在白色的<main>元素内

清除浮动修改版

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
/* 让 ::before 和 ::after 伪元素都显示出来 */
.clearfix::before,
.clearfix::after {
  /* 防止伪元素的外边距折叠 */
  display: table;
  content: " ";
}
/* 只有 ::after 伪元素需要清除浮动 */
.clearfix::after {
  clear: both;
}

这个版本使用 display: table 而不是 display: block
::before::after伪元素都加上这一属性,所有子元素的外边距都会包含在容器的顶部和底部之间。

提示
当我们不想要外边距折叠时,这个版本的清除浮动非常有用。

用什么版本的清除浮动取决于你。有些开发人员认为外边距折叠是CSS里的基础特性,因此他们选择不包含外边距。
不过以上两个版本的清除浮动都没有包含浮动元素的外边距,因此其他人会选择修改版以获得更一致的行为。两种观点都有自己的优势。

清除浮动和display: table

在清除浮动时使用 display: table 能够包含外边距,是因为利用了CSS的一些特性。
创建一个 display: table 元素(或者是本例的伪元素),也就在元素内隐式创建了一个表格行和一个单元格。
因为外边距无法通过单元格元素折叠,所以也无法通过设置了 display: table 的伪元素折叠。

看起来似乎使用 display: table-cell 也能达到相同的效果,但是clear属性只能对块级元素生效。
表格是块级元素,但是单元格并不是。因此,clear 属性无法跟 display:table-cell 一起使用。
所以要用 display: table 来清除浮动,同时利用隐式创建单元格来包含外边距。

出乎意料的"浮动陷阱"

现在页面里的白色容器已经能够包含浮动的媒体元素了,但是出现了另一个问题:四个媒体盒子没有如预期那样均匀地占据两行。
虽然前两个盒子(“Strength”和“Cadence”)符合预期,但是第三个盒子(“Change it up”)出现在了右边,也就是第二个盒子的下方,导致第一个盒子下面出现了一片非常大的空白。
这是因为浏览器会将浮动元素尽可能地放在靠上的地方,如图所示。

三个左浮动的盒子:如果盒子1比盒子2高,则盒子3不会浮动到最左边,而是浮动到盒子1的右边:

图4-10

因为盒子2比盒子1矮,所以它下面有多余的空间给盒子3。
盒子3会“抓住”盒子1,而不是清除盒子1的浮动。因此盒子3不会浮动到最左边,而是浮动到盒子1的右下角。

这种行为本质上取决于每个浮动块的高度。即使高度相差1px,也会导致这个问题。
相反,如果盒子1比盒子2矮,盒子3就没法抓住盒子1的边缘。除非以后内容改变导致元素高度发生变化,否则就不会看到这种现象。

众多的元素浮动到同一侧,如果每个浮动盒子的高度不一样,最后的布局可能千变万化。
同理,改变浏览器的宽度也会造成相同的结果,因为它会导致换行,从而改变元素高度。
而我们真正想要的是每行有两个浮动盒子,如下图所示。

每行两个元素:第二行的媒体元素应该清除第一行元素的浮动:

每行两个元素第

要想修复这个问题很简单:清除第三个浮动元素上面的浮动。
更通用的做法是,清除每行的第一个元素上面的浮动。由于已知每行有两个盒子,因此只需要清除每行的第奇数个元素上面那行的浮动即可。
你可以用:nth-child()伪类选择器选中这些目标元素。将代码添加到你的样式表中。

nth-child()选择器选取第奇数个媒体元素:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
.media {
  float: left;
  width: 50%;
  padding: 1.5em;
  background-color: #eee;
  border-radius: 0.5em;
}

/* 每个新行清除了上面一行的浮动 */
.media:nth-child(odd) {
  clear: left;
}

即使以后给页面添加更多元素,这段代码仍然有效。
它作用于第一、第三、第五个元素,等等。如果每行需要三个元素,则可以通过.media:nth-child(3n+1)来每隔两个元素选一个元素。

说明上面这种清除每行浮动的技术要求知道每行有几个元素。如
果宽度不是通过百分比来定义的,那么随着视口宽度的改变,每行的元素个数可能会变化。
这种情况下,最好使用别的布局方案,比如Flexbox或者inline-block元素。

接下来给媒体元素加上外边距来拉开距离。
前面的猫头鹰选择器也会给第一个元素之外的每个元素加上顶部外边距,因为这会导致第一行的元素无法对齐,所以还需要重置媒体元素的顶部外边距。
如代码清单所示,更新样式表。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
.media {
  float: left;
  /* 给每个媒体元素加上右侧和底部的外边距 */
  margin: 0 1.5em 1.5em 0;
  /* 从宽度里减去外边距,防止出现不必要的换行 */
  width: calc(50% - 1.5em);
  padding: 1.5em;
  background-color: #eee;
  border-radius: 0.5em;
}

.media:nth-child(odd) {
  clear: left;
}

给媒体元素加上右外边距后,一行放不下两个元素,因此需要用calc()从宽度里减去右外边距的值。

媒体对象和 BFC

现在四个灰色盒子已经布局好了,接下来看看里面的内容。
我们设想的是让图片在一侧,一段文字出现在图片的旁边。
这是一种很典型的网页布局,Web开发人员Nicole Sullivan把这种布局称作“媒体对象”。

媒体对象模式:图片在左边,一段描述内容在右边:

媒体对象模式

这种布局模式有好几种实现方案,包括Flexbox和表格布局,但这里我们用浮动。媒体对象的HTML标记如下所示。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<div class="media">
  <img class="media-image" src="shoes.png" width="80">
  <div class="media-body">
    <h4>Cadence</h4>
    <p>
      Check your stride turnover. The most efficient
      runners take about 180 steps per minute.
    </p>
  </div>
</div>

我给媒体对象的左边和右边分别添加了media-image和media-body类,以便选中和定位元素。
首先将图片浮动到左边。如图所示,仅仅将图片浮动到左边还不够。
如果文字很长,它会包围浮动元素。这是正常的浮动行为,但是不符合我们的需求。

文本包围了浮动的图片不符合要求

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
/* 将图片浮动到左侧 */
.media-image {
  float: left;
}

/* 删除猫头鹰选择器所添加的顶部外边距 */
.media-body {
  margin-top: 0;
}

/* 覆盖用户代理样式所添加的顶部外边距 */
.media-body h4 {
  margin-top: 0;
}

为了解决文字包围图片的问题,还要更加深入地了解浮动的工作原理。

BFC

如果在浏览器开发者工具里检查媒体正文(单击鼠标右键,选择检查或者检查元素),就会发现它的盒子扩展到了最左边,因此它会包围浮动的图片(如下图左边所示)。
现在文字围绕着图片,但是只要清除了图片底部的浮动,正文就会立刻移动到媒体盒子的左边。
而我们真正想要的是将正文的左侧靠着浮动图片的右侧排列(如下图右边所示)。

(左)媒体对象里的文字默认围绕浮动图片;(右)给媒体正文建立BFC之后,文字就不会跟浮动图片重叠

图4-14

为了实现右边这种布局,需要为正文建立一个块级格式化上下文(block formatting context, BFC)。
BFC是网页的一块区域,元素基于这块区域布局。虽然BFC本身是环绕文档流的一部分,但它将内部的内容与外部的上下文隔离开。
这种隔离为创建BFC的元素做出了以下3件事情。

(1) 包含了内部所有元素的上下外边距。它们不会跟BFC外面的元素产生外边距折叠。
(2) 包含了内部所有的浮动元素。
(3) 不会跟BFC外面的浮动元素重叠。

简而言之,BFC里的内容不会跟外部的元素重叠或者相互影响。如果给元素增加clear属性,它只会清除自身所在BFC内的浮动。
如果强制给一个元素生成一个新的BFC,它不会跟其他BFC重叠。

给元素添加以下的任意属性值都会创建BFC。

  • float: leftright,不为 none 即可。
  • overflow:hiddenautoscroll,不为 visible 即可。
  • display:inline-blocktable-celltable-captionflexinline-flexgridinline-grid。拥有这些属性的元素称为块级容器(block container)。
  • position: absoluteposition: fixed

说明
网页的根元素也创建了一个顶级的BFC。

使用 BFC 实现媒体对象布局

只要给媒体正文创建BFC,网页的布局就会符合预期(如图所示)。通常是给元素设置overflow值——hidden或者auto。

给所有媒体正文创建BFC

接下来设置样式表里overflow的值。按照代码更新你的样式表。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
.media {
  float: left;
  margin: 0 1.5em 1.5em 0;
  width: calc(50% - 1.5em);
  padding: 1.5em;
  background-color: #eee;
  border-radius: 0.5em;
}

.media:nth-child(odd) {
  clear: left;
}

.media-image {
  float: left;
  /* 给图片添加一个外边距,让它与正文中间出现间隔 */
  margin-right: 1.5em;
}

.media-body {
  /* 创建一个新的 BFC,这样正文就不会跟浮动的图片重叠 */
  overflow: auto;
  margin-top: 0;
}

.media-body h4 {
  margin-top: 0;
}

使用 overflow: auto 通常是创建BFC最简单的一种方式。
也可以使用前面提到的其他方式,但是有些问题需要注意,比如,使用浮动或者inline-block方式创建BFC的元素宽度会变成100%,
因此需要限制一下元素的宽度,防止因为过宽而换行,导致内容移动到浮动图片的下面。
相反,使用table-cell方式显示的元素,其宽度只会刚好容纳其中的内容,因此需要设置一个较大的宽度,强制使其填满剩余空间。

说明
某些情况下,BFC中的内容可能还会与别的BFC的内容重叠。
比如,内容溢出了容器(比如内容太宽)或者因为负外边距导致内容被拉到容器外面。

关于媒体对象的更多信息,请阅读Nicole Sullivan的大作The Media Object Saves Hundreds of Lines of Code。

网格系统

现在整个页面的布局已经创建好了,但是还存在一些不足。最主要的问题是,无法轻松地复用样式表中的内容。
现在媒体对象的宽度是50%,因此一行有两个元素。如果想要复用前面的设计,但需要一行放三个元素,那又该怎么办呢?

一种比较普遍的做法是借助网格系统提高代码的可复用性。
网格系统提供了一系列的类名,可添加到标记中,将网页的一部分构造成行和列。
它应该只给容器设置宽度和定位,不给网页提供视觉样式,比如颜色和边框。需要在每个容器内部添加新的元素来实现想要的视觉样式。

大部分流行的CSS框架包含了自己的网格系统。
它们的实现细节各不相同,但是设计思想相同:在一个行容器里放置一个或多个列容器。
列容器的类决定每列的宽度。接下来构建一个网格系统,这样你就能掌握它的工作原理,进而应用到网页中。

CSS框架 —— 一个预编译的CSS代码库,提供了Web开发中常见的样式模式。
它能够帮助快速搭建原型或者提供一个稳定的样式基础,辅助构建新样式。常见的框架包括 BootstrapFoundation 以及 Pure

理解网格系统

要构建一个网格系统,首先要定义它的行为。
通常网格系统的每行被划分为特定数量的列,一般是12个,但也可以是其他数。
每行子元素的宽度可能等于1~12个列的宽度。

展示了一个12列网格中不同的行。
第一行有6个1列宽的子元素和3个2列宽的子元素。
第二行有一个4列宽的子元素和一个8列宽的子元素。
因为每行子元素的宽度加起来都等于12列的宽度,所以刚好填满整行。

一个12列网格系统的两行

选取12作为列数是因为它能够被2、3、4、6整除,组合起来足够灵活。
比如可以很容易地实现一个3列布局(3个4列宽的元素)或者一个4列布局(4个3列宽的元素)。
还可以实现非对称的布局,比如一个9列宽的主元素和一个3列宽的侧边栏。在每个子元素里可以放置任意标记。

下面代码里的标记直观地展示了网格系统。每行有一个行容器div,在其中用column-n类为每个列元素放置一个div(n是网格里的列数)。

1
2
3
4
<div class="row">
  <div class="column-4"> 4 column</div>
  <div class="column-8"> 8 column</div>
</div>

构建网格系统

用网格系统改造一下前面的网页。虽然这种做法比前面实现布局的方式烦琐,但是因为它能提高CSS的可复用性,所以还是值得的。
按代码清单编辑你的HTML。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
<!doctype html>
<head>
  <style>
    :root {
      box-sizing: border-box;
    }

    *,
    ::before,
    ::after {
      box-sizing: inherit;
    }

    body {
      background-color: #eee;
      font-family: Helvetica, Arial, sans-serif;
    }

    body * + * {
      margin-top: 1.5em;
    }

    .row {
    }

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

    [class*="column-"] {
      float: left;
    }

    .column-1 { width: 8.3333%; }
    .column-2 { width: 16.6667%; }
    .column-3 { width: 25%; }
    .column-4 { width: 33.3333%; }
    .column-5 { width: 41.6667%; }
    .column-6 { width: 50%; }
    .column-7 { width: 58.3333%; }
    .column-8 { width: 66.6667%; }
    .column-9 { width: 75%; }
    .column-10 { width: 83.3333%; }
    .column-11 { width: 91.6667% }
    .column-12 { width: 100%; }

    header {
      padding: 1em 1.5em;
      color: #fff;
      background-color: #0072b0;
      border-radius: .5em;
      margin-bottom: 1.5em;
    }

    .main {
      padding: 0 1.5em;
      background-color: #fff;
      border-radius: .5em;
    }

    .container {
      max-width: 1080px;
      margin: 0 auto;
    }

    .media {
      float: left;
      margin: 0 1.5em 1.5em 0;
      width: calc(50% - 1.5em);
      padding: 1.5em;
      background-color: #eee;
      border-radius: 0.5em;
    }

    .media:nth-child(odd) {
      clear: left;
    }

    .media-image {
      float: left;
      margin-right: 1.5em;
    }

    .media-body {
      overflow: auto;
      margin-top: 0;
    }

    .media-body h4 {
      margin-top: 0;
    }

    .clearfix::before,
    .clearfix::after {
      display: table;
      content: " ";
    }
    .clearfix::after {
      clear: both;
    }
  </style>
</head>

<body>
  <div class="container">
    <header>
      <h1>Franklin Running Club</h1>
    </header>

    <main class="main clearfix">
      <h2>Running tips</h2>

      <div class="row">
        <div class="column-6">
          <div class="media">
            <img class="media-image" src="runner.png" width="80">
            <div class="media-body">
              <h4>Strength</h4>
              <p>
                Strength training is an important part of
                injury prevention. Focus on your core&mdash;
                especially your abs and glutes.
              </p>
            </div>
          </div>
        </div>

        <div class="column-6">
          <div class="media">
            <img class="media-image" src="shoes.png" width="80">
            <div class="media-body">
              <h4>Cadence</h4>
              <p>
                Check your stride turnover. The most efficient
                runners take about 180 steps per minute.
              </p>
            </div>
          </div>
        </div>
      </div>

      <div class="row">
        <div class="column-6">
          <div class="media">
            <img class="media-image" src="shoes.png" width="80">
            <div class="media-body">
              <h4>Change it up</h4>
              <p>
                Don't run the same every time you hit the
                road. Vary your pace, and vary the distance
                of your runs.
              </p>
            </div>
          </div>
        </div>

        <div class="column-6">
          <div class="media">
            <img class="media-image" src="runner.png" width="80">
            <div class="media-body">
              <h4>Focus on form</h4>
              <p>
                Run tall but relaxed. Your feet should hit
                the ground beneath your hips, not out in
                front of you.
              </p>
            </div>
          </div>
        </div>
      </div>
    </main>
  </div>
</body>

以上代码将每两个媒体对象用一个行包起来,在行内把每个媒体对象都单独放在了一个6列宽的容器中。

样式代码仅仅实现了清除浮动。这样写是为了避免每添加一个行元素就要给它加一个clearfix类。
稍后会给上面的代码补充更多内容。不过基本上行元素的作用就是给列元素提供一个容器,将列元素包裹起来,而清除浮动恰好能起到这个作用。

给列元素添加初始样式。这才是“重头戏”,但是代码并不复杂。将所有的列都浮动到左边,给每种列元素指定宽度值。

给所有的列加上float: left后,要给每种列元素单独设置宽度。可能需要花点时间计算出不同列的宽度百分比:所需列数除以总列数(12)。
要精确到小数点后几位,以免因为四舍五入而导致误差。

现在网格系统已经初具雏形。你的页面现在看起来应该如图所示。看起来并不是理想的效果,因为媒体对象重复实现了网格系统的一些样式。

图4-17

现在简化一下媒体对象的样式。去掉左浮动,因为网格系统已经包含了这条规则。
去掉宽度,这样它才能填满容器100%的宽度。这里的容器是一个6列的元素,它的宽度恰好是我们想要的。
去掉外边距和用来清除浮动的nth-child选择器。网页现在应该如图所示。

图4-18

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
.media {
  padding: 1.5em;
  background-color: #eee;
  border-radius: 0.5em;
}

.media-image {
  float: left;
  margin-right: 1.5em;
}

.media-body {
  overflow: auto;
  margin-top: 0;
}

.media-body h4 {
  margin-top: 0;
}

因为删除了媒体对象的所有外边距,包括底部外边距,所以最后一行的媒体对象和容器底部之间的间隔丢失了。给容器加上底部内边距,以恢复间隔

给主容器添加底部内边距:

1
2
3
4
5
.main {
  padding: 0 1.5em 1.5em;
  background-color: #fff;
  border-radius: .5em;
}

添加间隔

现在网格系统还缺少每列之间的间隔。接下来就加上这些间隔,同时补充一些细节样式。完成后的页面看起来应该如图所示。

图4-19

给每个网格列添加左右内边距,创造间隔。把
间隔交给网格系统实现,而不是让内部的组件(比如媒体对象)自己实现,这样就能够在其他页面复用这套网格系统,不用再费心去实现间隔。

因为需要列之间有1.5em的间隔,所以可以将其分成两半,给每个列元素左右各添加一半的内边距。按照代码清单4-17修改网格的样式。这里还去掉了所有列元素的顶部外边距,覆盖猫头鹰选择器里的样式规则。

给网格系统添加间隔:

1
2
3
4
5
6
7
[class*="column-"] {
  float: left;
  /* 给每个列元素的左右内边距各赋值 0.75em */
  padding: 0 0.75em;
  /* 去掉列元素的顶部外边距 */
  margin-top: 0;
}

现在网格系统的每个列元素之间都有1.5em的间隔,看起来棒棒的。
但是,这段代码导致了网格列和网格行外的内容出现轻微的错位。
如图所示,页面标题(“Running tips”)的左边缘本来应该跟第一列的媒体对象的边缘对齐,但是列的内边距让媒体对象所在的灰色盒子稍微往右移了一点。

页面标题的左侧跟列元素(虚线区域)的左侧对齐了,但没有跟列里面的内容对齐:

图4-20

可以去掉每行第一列的左侧内边距和最后一列的右侧内边距来解决这个问题,但这需要添加一堆样式规则,其实只需要调整行的宽度即可。

使用负外边距来拉伸行元素的宽度。给行元素添加一个−0.75em的左侧外边距,把行元素的左侧拉伸到容器外面。
列元素的内边距会把里面的内容往右推0.75em,第一列就会跟页面标题左对齐(如图所示)。
同理,还要给行元素添加负的右侧外边距, 拉伸右侧。

给行元素添加负的外边距,让其向左延伸,抵消列元素的内边距,这样列元素里的内容就能跟网页的标题对齐了:

图4-21

具体实现如代码清单所示。现在不管将行元素放在哪里,它都会被拉伸,比它所在的容器宽1.5em。
列元素的内边距会将其内容往回移动,跟行元素外面的容器边缘对齐。
这种方法也是双容器模式的一个不错的修改版。在双容器模式中,行元素对其所在容器而言就是内层容器。

1
2
3
4
.row {
  margin-left: -0.75em;
  margin-right: -0.75em;
}

现在你已经借助浮动实现了一个完善的网格系统。不管用这个网格系统还是CSS框架里的网格系统,你现在都应该理解了它的工作原理。最终的样式表如代码清单所示。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
<!doctype html>
<head>
  <style>
    :root {
      box-sizing: border-box;
    }

    *,
    ::before,
    ::after {
      box-sizing: inherit;
    }

    body {
      background-color: #eee;
      font-family: Helvetica, Arial, sans-serif;
    }

    body * + * {
      margin-top: 1.5em;
    }

    .row {
      margin-left: -0.75em;
      margin-right: -0.75em;
    }

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

    [class*="column-"] {
      float: left;
      padding: 0 0.75em;
      margin-top: 0;
    }

    .column-1 { width: 8.3333%; }
    .column-2 { width: 16.6667%; }
    .column-3 { width: 25%; }
    .column-4 { width: 33.3333%; }
    .column-5 { width: 41.6667%; }
    .column-6 { width: 50%; }
    .column-7 { width: 58.3333%; }
    .column-8 { width: 66.6667%; }
    .column-9 { width: 75%; }
    .column-10 { width: 83.3333%; }
    .column-11 { width: 91.6667% }
    .column-12 { width: 100%; }

    header {
      padding: 1em 1.5em;
      color: #fff;
      background-color: #0072b0;
      border-radius: .5em;
      margin-bottom: 1.5em;
    }

    .main {
      padding: 0 1.5em 1.5em;
      background-color: #fff;
      border-radius: .5em;
    }

    .container {
      max-width: 1080px;
      margin: 0 auto;
    }

    .media {
      padding: 1.5em;
      background-color: #eee;
      border-radius: 0.5em;
    }

    .media-image {
      float: left;
      margin-right: 1.5em;
    }

    .media-body {
      overflow: auto;
      margin-top: 0;
    }

    .media-body h4 {
      margin-top: 0;
    }

    .clearfix::before,
    .clearfix::after {
      display: table;
      content: " ";
    }
    .clearfix::after {
      clear: both;
    }
  </style>
</head>

<body>
  <div class="container">
    <header>
      <h1>Franklin Running Club</h1>
    </header>

    <main class="main clearfix">
      <h2>Running tips</h2>

      <div class="row">
        <div class="column-6">
          <div class="media">
            <img class="media-image" src="runner.png" width="80">
            <div class="media-body">
              <h4>Strength</h4>
              <p>
                Strength training is an important part of
                injury prevention. Focus on your core&mdash;
                especially your abs and glutes.
              </p>
            </div>
          </div>
        </div>

        <div class="column-6">
          <div class="media">
            <img class="media-image" src="shoes.png" width="80">
            <div class="media-body">
              <h4>Cadence</h4>
              <p>
                Check your stride turnover. The most efficient
                runners take about 180 steps per minute.
              </p>
            </div>
          </div>
        </div>
      </div>

      <div class="row">
        <div class="column-6">
          <div class="media">
            <img class="media-image" src="shoes.png" width="80">
            <div class="media-body">
              <h4>Change it up</h4>
              <p>
                Don't run the same every time you hit the
                road. Vary your pace, and vary the distance
                of your runs.
              </p>
            </div>
          </div>
        </div>

        <div class="column-6">
          <div class="media">
            <img class="media-image" src="runner.png" width="80">
            <div class="media-body">
              <h4>Focus on form</h4>
              <p>
                Run tall but relaxed. Your feet should hit
                the ground beneath your hips, not out in
                front of you.
              </p>
            </div>
          </div>
        </div>
      </div>
    </main>
  </div>
</body>

现在完全借助浮动实现了页面布局。虽然浮动有些怪异的行为,但它仍然能实现我们的需求。对浮动行为有了更深刻的理解后,希望你不会再对浮动犯怵。

总结

  • 浮动的设计初衷是让文字围绕一个元素排列,但有时这种效果并不是我们想要的。
  • 使用清除浮动来包含浮动元素。
  • BFC有3个好处:包含浮动元素,防止外边距折叠,防止文档流围绕浮动元素排列。
  • 使用双容器模式让页面内容居中。
  • 使用媒体对象模式将描述文字定位到图片旁边。
  • 使用网格系统实现更丰富的网页布局。

其他

你应该对浮动元素不陌生。从 Netscape 1.1 开始,就可以浮动图像,例如 <img src="b5.gif" align="right">
此时,图像浮动到右侧,其他内容(例如文本)将围绕图像流动。
其实,“浮动”这个名称就出自 Netscape DevEdge 中的 "Extensions to HTML 2.0" 页面,转摘如下:

对 ALIGN 属性的扩展需要特别说明一下。先看 "left" 和 "right" 两个值。
使用它们设置图像的对齐方式得到的是全新的浮动图像类型。

以前,只有图像能浮动,某些浏览器还支持浮动表格。
而使用 CSS 可以浮动任何元素,从图像、段落到列表,不一而足。在 CSS 中,浮动通过 float 属性实现

1
2
3
4
5
6
7
float
取值 left | right | none
初始值 none
适用于 所有元素   
计算值 指定的值
继承性 否
动画性 否

例如,若想把图像浮动到左侧,可以使用下述标记:

1
<img src="b4.gif" style="float: left;" alt="b4">

这个图像会 "浮动" 到窗口左边,文本则围绕图像流动。这正是我们预期的行为。

然而,利用 CSS 浮动元素也带来了一些棘手的问题

浮动的元素

浮动元素后要注意几件事。
首先,在某种程度上讲,浮动的元素脱离了常规的文档流,不过对布局仍有影响。

之所以这样,是因为元素浮动后,其他内容将围绕它流动。
浮动的图像就是这样,不过即使浮动段落,也是如此。

浮动详解

在深入了解浮动之前,一定要建立容纳块的概念。浮动元素的容纳块是最近的块级祖辈元素。
因此,在下述标记中,浮动元素的容纳块是所在的段落元素。

1
2
3
4
5
6
<p>
  This is paragraph text, but you knew that. Within the content of this
  paragraph is an image that's been floated.
  <img src="testy.gif" style="float: right;">
  The containing block for the floated image is the paragraph.
</p>

此外,不管元素是什么类型,浮动后得到的都是块级框。
因此,如果浮动的是链接,即使元素自身正常情况下生成的是行内框,浮动后生成的也是块级框。
在布局中,浮动元素就像 div 元素一样。这与浮动元素声明 display: block 的效果没什么不同,不过没必要多此一举

浮动元素的位置由一系列规则约束,在深入探讨具体行为之前,先来了解一下。
这些规则与计算外边距和宽度的方式略有相似之处,而且都有些符合常识的地方。这些规则如下。

1. 浮动元素的左(或右)外边界不能超过容纳块的左(或右)内边界

2. 如果文档源码中处于前面的元素向左浮动,那后面的浮动元素的左外边界必定在前一个元素右外边界的右侧,除非后一个元素的顶边在前一个元素的底边以下。
类似地,如果在文档中处于前面的元素向右浮动,后面的浮动元素的右边外界必定在前一个元素左外边界的左侧,除非后一个元素的顶边在前一个元素的底边以下

3. 左浮动元素的右外边界不能在右浮动元素的左外边界的右侧。右浮动元素的左外边界不能在左浮动元素的右外边界的左侧。

4. 浮动元素的顶边不能比父元素的内顶边搞。如果浮动元素位于两个折叠的外边距之间,在两个元素之间放置它的位置时,将视其有个块级父元素

5. 浮动元素的顶边不能比前方任何一个浮动元素或块级元素的顶边高。

6. 浮动元素的顶边不能高于文档源码中出现在浮动元素之前的元素生成的框体所在的行框的顶边

7. 左浮动元素的左边如果还有一个向左浮动的元素,那么它的右外边界不能在容纳块右边界的右侧。类似地,右浮动元素的右边如果还有一个向右浮动的元素,那么它的右外边界不能在容纳块左边界的左侧。

8. 浮动元素必须放在尽可能高的位置上。

9. 左浮动元素必须尽量向左移动,右浮动元素必须尽量向右移动。位置越高,向左或向右移动的距离约远