Skip to content

Flexbox

如果你在过去的几年里一直在接触CSS,那么肯定听过有人对Flexbox大加赞赏。
Flexbox,全称弹性盒子布局(Flexible Box Layout),是一种新的布局方式。
跟浮动布局相比,Flexbox的可预测性更好,还能提供更精细的控制。
它也能轻松解决困扰我们许久的垂直居中和等高列问题。

Flexbox已经问世好几年了,面向高级浏览器编程的开发人员也用了一段时间。
现在Flexbox已经得到主流浏览器的支持,在IE10中也得到了部分支持。实际上,Flexbox比border-radius属性的支持范围更广(Opera Mini不支持border-radius)。

要说Flexbox有缺点的话,那一定是它那数不清的选项了。
它给CSS引入了12个新属性,包括一些缩写属性。一次掌握所有的属性实在吃不消。
当我第一次学习Flexbox时,有点像拿着高压水枪喝水,东西太多了,还很难将所有属性记住。
这次我要用另一种方式来教大家学习Flexbox——由浅入深地来。

弹性盒简介

弹性盒为 CSS 添加了一种新的布局模式。我们已经学过块级元素和行内块级元素,还知道表格和定位元素也是 CSS 布局的方式。
弹性盒(flexbox, flexible box 的简称)则能实现另一种布局模式 -- 弹性布局

弹性盒题提供了很多有用的属性,无需使用浮动或 inline-block 值就能把多个元素排成一行。其实,因为弹性盒是“弹性的”,所以弹性容器里的元素能自动调整宽度,这一点与宽度是百分比值的浮动元素特别相似。

表面上看,弹性盒特别简单,只需提供以下两个组件:

弹性容器:
任何 HTML 元素都可以作为弹性容器,不过通常使用<div>标签或其他 HTML 结构标签。弹性容器标签里的子代和其他标签组成弹性盒模型的另一部分

弹性项目:
直接嵌套在弹性容器里的标签叫弹性项目。弹性容器的每个直接子代都会自动变成弹性项目。弹性容器里可以放置任何 HTML 标签。
而且,各个子代标签甚至无需是同一种类型。例如,一个弹性容器里可以有一个段落和四个<div>标签,这些元素都是弹性项目。

记住,只有弹性容器的子代元素是弹性项目。如果把一个<div>标签设为弹性容器,然后就在里面放一个无序列表,那么只有<ul>标签是弹性项目,<ul>标签里嵌套的<li>标签则不是

也就是说,使用弹性盒的方式很简单,只需在页面中放一个<div>标签,然后在里面再嵌套几个<div>标签,现在我们可能已经习惯了这种做法。
例如,使用弹性盒可以轻易把下述简单的 HTML 结构显示在一行里:

1
2
3
4
5
<div class="container">
  <div>A flex item</div>
  <div>Another flex item</div>
  <div>A third flex item</div>
</div>

外层<div>标签是容器,内层<div>标签则是子代元素。浏览器会把这些子代<div>标签显示为块级元素,纵向叠放着,占满外层<div>标签的全部宽度。

不过,把最外层那个<div>标签的 display 属性设为 flex,就能把它变成弹性容器,如下所示:

1
2
3
.container {
  display: flex;
}

加入这一行 CSS 代码后,布局就变了。各个子代<div>标签自动变成了弹性项目,并排显示成一行。我们没使用浮动货哦行内块级元素就做到了这一点

最后,如果想让容器里各个<div>标签的宽度相等,并且在一起填满整个容器,只需把这些<div>标签的 flex 属性设为 1,如下所示:

1
2
3
.container div {
  flex: 1;
}

一般情况下,弹性项目是相互接触的,因此,可能想在项目之间添加一些空白。这个需求有多种做法,不过对这个示例来说,下述方法最简单:

1
2
3
.container div:nth-of-type(1n+2) {
  margin-left: 20px;
}

这个:nth-of-type选择符选取第二个<div>标签之后的(含)每个<div>标签,把选中的<div>标签的左外边距设为 20 像素(这个实用的技巧能避免为第一个<div>标签设置外边距,防止它从容器的外界向里缩进)

弹性盒这个概念特别简单。从前文可以看出,实现所需的效果用不了多少 CSS。弹性盒的主要优势是,无需像浮动的元素那样,担心元素会从容器里溢出,而且可以轻易创建等高的列。
弹性盒唯一的难点是,要理解众多相关的属性,以及如何跳出思维的局限,自由组合各个属性。

弹性容器相关的属性

弹性容器和弹性项目都有各自相关的一系列 CSS 属性,用于控制浏览器显示二者的方式。有一部分属性专门针对弹性容器,其中最重要的是 display 属性。
如果想把 HTML 标签变成弹性盒,要这么做:

1
display: flex;

记住,这个属性要应用在容器元素上,容器里面的其他标签则变成弹性项目。弹性项目甚至也可以变成弹性容器,以此实现有趣的效果。

flex-flow 属性

默认情况下,弹性项目并排显示成一行。而且,浏览器只在一行里显示各个弹性项目,不会换行。
也就是说,不管浏览器窗口有多窄,即使窄到内容从弹性项目中溢出了(如图 17-3 左边),各个弹性项目始终会并排显示,不会有弹性项目显示在下一行

flex-flow 属性既能控制弹性项目的排列方向,也能控制是否换行。这个属性需要两个值,之间用空格分开。第一个值指明方向,第二个值指明是否换行。例如:

1
2
3
4
.container {
  display: flex;
  flex-flow: column-reverse nowrap;
}

图17-3flexflow

图17-3 通常,弹性项目不会换行。如果浏览器窗口特别窄,弹性项目里的内容还会从边界溢出(左边)。不过,弹性项目也可以纵向叠放,甚至还能颠倒显示的顺序(右边)

flex-flow 属性的第一个值控制方向,有四个可选值:

  • row: 这是默认设置,各个弹性项目并排显示,HTML 源码中的第一个项目显示在最左边,源码中的最后一个项目显示在最右边(如图 17-3 左边)
  • row-reverse: 各个弹性项目也并排显示,不过在屏幕上的顺序是颠倒的。也就是说,HTML 源码中的最后一个项目显示在容器的最左边,源码中的第一个项目显示在容器的最右边
  • column: 各个弹性项目纵向叠放。这是一组<div>标签常规的显示方式,因此可能不常使用这个值,不过,使用媒体查询为移动设备设计时,用得到。
    我们可以在针对小屏幕移动设备的媒体查询中把方向设为 column,把并排显示成一行的的弹性项目(针对较大屏幕的布局)变为纵向叠放
  • column-reverse: 与 column 设置的作用类似,不过弹性项目的显示顺序相反。HTML 源码中最后一个项目显示在容器的顶部(如图 17-3 右边)

flex-flow 属性的第二个值控制是否换行,把弹性项目显示在下一行(方向为 row 时)或下一列(方向为 column 时)。有三个可选值:

  • nowarp: 这是弹性项目在弹性容器里的常规行为。不管浏览器窗口有多窄,浏览器都会在一行里显示所有弹性项目(如图 17-3 左边)。方向为 column 时,浏览器会纵向排序各个弹性项目(如图 17-3 右边)
  • wrap: 容器里放不下的弹性项目,显示在下一行(或下一列),为了让弹性项目能换到下一行(或下一列),在弹性项目上还要设置一些属性
  • wrap-reverse: 与 wrap 设置的作用类似,不过以相反的顺序换行

注意: flex-flow 属性是另两个弹性盒相关的 CSS 属性的简写形式: flex-direction 和 flex-wrap。例如:

1
flex-flow: row: wrap;

等价于:

1
2
flex-direction: row;
flex-wrap: wrap;

因为使用 flex-flow 属性写的代码较少,所以推荐使用简写形式

justify-content 属性

justify-content 属性告诉浏览器把弹性项目显示在一行里的什么位置。只有为弹性项目设置了宽度,而且各个项目的宽度之和小于弹性容器的宽度时,这个属性才起作用。
如果宽度是弹性的,justify-content 属性完全没效果。这个属性有五个可选值:

  • flex-start: 项目在一行里靠左对齐。让人难以理解的是,如果把方向设为 row-reverse,这个选项会靠右对齐各个项目
  • flex-end: 项目在一行靠右对齐。当然,如果把方向设为 row-reverse,项目就靠左对齐
  • center: 弹性项目据中国显示在容器中间
  • space-between: 均匀各个弹性项目,项目之间的空间大小相同,最左边的项目靠容器的左边,最右边的项目靠容器右边。这个选项可以让一组按钮占满整个容器的宽度,例如,在页面顶部的导航栏里均布导航按钮,或者在博客文章下面显示分页链接
  • space-around: 把容器里剩余的空间平均分给各个项目,包括最左边和最右边的项目

这个属性要添加到弹性容器的样式里。此外,还要为项目设置宽度。

1
2
3
4
.container {
  display: flex;
  justify-content: space-around;
}

align-items 属性

align-items 属性决定高度不同的弹性项目在弹性容器里的纵向对齐方式。默认情况下,弹性项目会拉伸,因此高度都相同(如图 17-6 中的标号 5)。
不过,还有几个其他选项:

  • flex-start: 把各个弹性项目的顶边与容器的顶边对齐(如图 17-6 中的标号1)
  • flex-end: 把各个弹性项目的底边与容器的底边对齐(如图 17-6 中的标号2)
  • center: 把各个弹性项目的纵向中心线与容器的纵向中心线对齐(如图 17-6 中的标号3)
  • baseline: 把各个弹性项目里第一个元素的基线对齐(如图 17-6 中的标号 4)
  • stretch: 这是弹性项目常规的纵向对齐方式,拉伸容器里的各个项目,使各个项目的高度相同(如图 17-6 中的标号 5)。这个效果使用其他 CSS 技术特别难以实现

图17-6alignitems

图17-6 如果弹性项目的高度不同,想控制项目在容器里的纵向对齐方式,可以使用 align-items 属性。纵向对齐方法还可以在单个项目上设定,方法是使用 align-self 属性

如果容器的方向是 column,这些选项的效果有所不同。那时,align-items 属性用于控制宽度不同的纵排弹性项目在弹性容器里的横向对齐方式。

align-items 属性要添加到弹性容器的样式里。例如,如果想实现图 17-6 中标号 2 所示的布局,可以使用下述 CSS 代码:

1
2
3
4
.container {
  display: flex;
  align-items: flex-end;
}

align-content 属性

可以应用到弹性容器上的最后一个属性是 align-content。这个属性告诉浏览器如何放置显示多行的弹性项目。
只有满足两个条件,align-content 属性才会起作用: 第一,弹性容器必须允许换行;第二,弹性容器的高度必须大于多行显示的弹性项目。
也就是说,容器的高度必须比各行项目的高度之和大,有足够的纵向空间。通常,这不是问题,但有时空间会不足

align-content 属性支持六个值:

  • flex-start: 把各行弹性项目放在弹性容器的顶部(如图 17-7 中的标号 1)
  • flex-end: 把各行弹性项目放在弹性容器的底部(如图 17-7 中的标号 2)
  • center: 把各行整体的纵向中心线与容器的纵向中心线对齐(如图 17-7 中的标号 3)
  • space-between: 把纵向额外的空间平均分布在各行之间,最上面一行放在容器的顶部,最下面一行放在容器的底部(如图 17-7 中的标号4)
  • space-around: 把空间平均分布到各行的上下,包括最上面一行和最下面一行(如图 17-7 中的标号5)
  • stretch: 这是常规行为,即拉伸各个项目,让一行里的项目具有相同的高度。注意,如果项目里的内容不一样多,各行的高度不相同。 例如,对于图 17-7 中标号 6 那个示例来说,下面一行项目里的内容比上一行项目里的内容少,所以下面一行稍微矮一点

图17-7align-content

图17-7 只有弹性容器和弹性项目满足特定的条件,align-content 属性才起作用: 第一: 容器允许换行; 第二,弹性项目分成多行,而且容器的高度比各行的高度之和大。也就是说,不会经常使用这个属性。

align-content 属性要添加到弹性容器的样式里。此外,还要确保 flex-flow 属性包含 wrap 选项,而且容器的高度要比各行的高度之和大。
例如,如果想实现图 17-7 中标号 5 所示的布局,可以使用下述 CSS 代码:

1
2
3
4
5
6
.container {
  display: flex;
  flex-flow: row wrap;
  align-content: space-between;
  height: 600px;
}

注意: 弹性容器不是块级元素,因此有些属性不能应用到弹性容器或弹性项目上。
例如,column 属性不能应用于弹性容器,float 和 clear 属性不能应用于弹性项目

弹性项目相关的属性

为弹性容器设置属性只是开始,还有些属性是应用在弹性项目(弹性容器的直接子代)上的,用于控制项目的显示顺序、项目的宽度,以及项目在容器里的对齐方式。

order 属性

Web 初期,内容按照出现在 HTML 文件中的顺序,从浏览器窗口顶端开始向下显示。如果是想发布学术论文,这种线性结构很好。
图形设计师接触 Web 后,开始使用各种技术把内容分成行和栏;一开始使用表格,后来则使用 CSS 浮动

可是,即便设计师发挥聪明才智找到了方法,能打破网页从上到下这种常规的单栏布局,还是会受到 HTML 文件中源码顺序的束缚。
例如,如果想创建三栏布局,一个侧边栏,一个正文主栏,再加一个侧边栏,最常见的方法是创建三个<div>标签,然后使用浮动,把它们变成分栏:

1
2
3
4
5
6
7
8
9
<div class="sidebar1">
  <!-- 导航等内容 -->
</div>
<div class="main">
  <!-- 正文,访客查看这个页面寻找的内容,这里是最重要的内容 -->
</div>
<div class="sidebar2">
  <!-- 更多(不太重要的)内容 -->
</div>

order 属性用于为弹性项目设置数值,指明项目在行(或列)里的位置。这样一来,HTML 源码的顺序彻底没关系了。
我们可以让 HTML 源码中的最后一个元素显示在一行的开头,或者让第一个元素显示在一行的末尾

比如说我们还是想创建前面那个三栏布局: 左边一个侧边栏,中间是主要内容,右边也有一个侧边栏。不过,这一次我们可以重新组织 HTML 代码,把主内容放在最前面,让源码对搜索引擎和屏幕阅读器都友好:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<div class="content">
  <div class="main">
    <!-- 正文,访客查看这个页面寻找的内容,这里是最重要的内容 -->
  </div>
  <div class="sidebar1">
    <!-- 导航等内容 -->
  </div>
  <div class="sidebar2">
    <!-- 更多(不太重要的)内容 -->
  </div>
</div>

然后,把外层<div>标签(在这里是类为 content 的那个标签)变成弹性容器,再使用 order 属性,以所需的任何顺序排列里面的<div>标签:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
.content {
  display: flex;
}
.sidebar1 {
  order: 1;
}
.main {
  order: 2;
}
.sidebar2 {
  order: 3;
}

现在,虽然主要内容先出现在 HTML 代码中,但是会显示在两个侧边栏中间,而第一个侧边栏显示在弹性容器左边。

order 属性的数值与 z-index 属性的数值作用类似,因此不必要使用连续的数字、例如1、2和3,浏览器会按照从小到大的顺序排列。
例如,在上述示例中,可以把 1、2 和 3 换成 10、20 和 30,或者 5、15 和 60.
可是,为了明确表明意图,应该使用易于理解的数字序列。例如,使用 1、2、3、4 等最容易理解。

不过,有时可能只想把一列移到最左边或最右边。此时,只需为想移动的项目设置 order 属性,其他项目都别设置。
例如,上述代码可以像下面这样简化,现在第一个侧边栏依然会移动最左边:

1
2
3
4
5
6
.content {
  display: flex;
}
.sidebar1 {
  order: -1;
}

我们设置的值是 -1,因此项目会被移到弹性容器的左边,显示在其他项目之前。
相反地,把第一个侧边栏的 order 属性设为 1,而其他元素的 order 属性都不设置,那个侧边栏会移到最右边。

注意: 把弹性容器的方向设为 column 时,order 属性控制的是弹性项目从上到下的叠放顺序: 数字小的弹性项目在数字大的弹性项目的上边。
此外,方向还可以设为 column-reverse 和 row-reverse,颠倒行和列的顺序。所以,考虑到顺序被颠倒了,此时要相应地调整数字。

align-self 属性

align-self 属性的作用与弹性容器的 align-items 属性类似。不过,align-items 属性应用于容器里的所有弹性项目,而 align-self 属性只应用于单个弹性项目。
这个属性应用于单个项目上(而不是容器上),它会覆盖 align-items 属性的值。
也就是说,我们可以让容器里的所有弹性项目都靠容器上部对齐,但是只让某一个项目靠底部对齐。

align-self 属性的可选值与 align-items 属性一样,而且相应的效果也一样。唯一的不同是,align-self 属性只能应用于单个弹性项目

小教程:自动确定弹性项目的外边距

弹性项目有个令人惊叹的特性: 外边距不折叠。乍看起来,这不算什么,但是利用这个巧妙的特性,可以把外边距的值设为 auto,让浏览器根据可用空间调整外边距的值。

 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
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Flexbox</title>
  <link href="https://fonts.googleapis.com/css?family=Raleway:400,300,600" rel="stylesheet" type="text/css">
  <link rel="stylesheet" href="base.css">
  <style>
   .banner {
     display: flex;
     align-items: flex-end;
   }
   .logo {
      margin-right: auto;
   }
  </style>
<script>

</script>
</head>
<body>
<div class="section">
  <div class="banner">
    <p class="logo">Our Company</p>
    <a href="#">Our Clients</a>
    <a href="#" class="highlight">About Us</a>
    <a href="#">Careers</a>
  </div>
</div>
</body>
</html>

自动确定弹性项目的外边距效果

对弹性项目来说,把外边距设为 auto 的意思是,让浏览器根据可用空间自行设置外边距的大小。
在这个例子中,徽标和导航按钮不能填满整个横幅,因此,把右外边距设为 auto 的意思是,让浏览器把横幅中可用的空间(不管有多少)放到“Our Company”的右边。
这么做的效果是,把导航栏向右推,直至横幅的另一边。太棒了。

把第一个按钮的左外边距设为 auto 也能实现相同的效果,此时,全部可用空间都添加在第一个按钮的左边,把所有按钮向右推

flex 属性

使用目前所学的基本属性可以做很多事情,不过弹性盒的“弹性”要靠 flex 属性实现。
这个属性是控制弹性项目宽度的关键,使用它能轻易实现“弹性的”列,或者根据容器的尺寸变化而改变宽度,即便容器的尺寸是未知的或者动态变化的,也能做到这一点。
使用 flex 属性能做响应式 Web 设计,而且用时少,也不要做太多数学计算。

提供给 flex 属性的第一个值是一个数字,指明弹性项目的相对宽度。假如某个弹性容器里有三个<div>标签,我们可以把这些标签的 flex 属性设为 1,让它们的宽度相同:

1
2
3
4
5
6
.container {
  display: flex;
}
.container div {
  flex: 1;
}

三个<div>标签的值相同,因此它们的宽度都相等(如图 17-11 上部)。我们提供给 flex 属性的值是相对的单位,而不是绝对的度量值。

图17-11弹性项目设置flex属性

图17-11: 设置 flex 属性后,弹性项目的宽度会随着容器的宽度而变宽或收窄

在图 17-11 上部那么示例中,各个弹性项目的宽度约为容器宽度的 33%。
可是,如果某个<div>标签的 flex 属性值为 2,那么各自的宽度就变了(如图 17-11 中间): 左边两个<div>标签的宽度是一个单位,而右边那个<div>标签的宽度是两个单位。
也就是说,右边那个<div>标签的宽度是另两个<div>标签的两倍,所以右边那个<div>标签占弹性容器空间的一半。

因此,弹性项目的真实宽度由两个因素确定: 在 CSS 中为 flex 属性设置的值和容器中弹性项目的数量。

flex 属性的第二个值也是数字,不过设定的是 flex-shrink 属性。当容器的宽度没有各个弹性项目的宽度之和大时,flex-shrink 属性会起作用。
此时,flex-shrink 属性控制弹性项目能变得多窄,或者弹性项目能收窄多少。能收窄的量取决于为弹性项目设置的宽度,即 flex 属性的最后一个值。

最后一个值针对 flex-basis 属性,其作用是设置弹性项目的基准宽度。这个值可以用绝对值,如 100px 或 5em,也可以使用百分比值,如 50%。
为 flex-basis 属性设置的值可以理解为弹性项目的最小宽度。flex-basis 属性的值就是弹性项目的宽度,不过具体的宽度由 flex 属性中的其他值决定,可能大于 flex-basis 属性的值,也可能小于

图17-12flex属性解析

图17-12 如果不设置 flex-basis 属性的值,而且把 felx-grow 属性的值设为 0,那么叹息你给项目会一直收窄,能多窄收多窄。
例如,在底部那个图像中,前两个弹性项目的样式中有flex: 0 1;没有设置最小宽度,也没有设置 flex-grow 属性的值,因此前两个项目会收窄。
而右边最后那个项目的 flex 属性值是 1,所以会弹性变化,宽度增大,占满一行里剩下的全部可用空间

例如,在图 17-12 上部那个图像中,flex-basis 属性的值是 250px。第一个值是 0,这个值用于确定项目如何变宽。设为 0 时,不会变宽,因此弹性项目的宽度与 flex-basis 属性的值相等,即 250 像素

然而,只要第一个值大于 0,整行的宽度就会被占满。因此,在图 17-12 中间那个图像中,虽然各个弹性项目的 flex-basis 属性的值是 250px,但是各个弹性项目的真实宽度有所不同,这是因为 flex-grow 属性的值不同,分别为 1、2 和 4

数学分析

那么,flex-grow 和 flex-basis 两个属性的值都设置时,浏览器如何确定弹性项目的宽度呢?
请集中注意力,现在我们要做些数学计算。计算过程虽然不难,但是其中涉及很多加减乘除运算。

图17-13flex数学分析

图17-13 弹性项目的真实宽度由弹性容器的宽度、flex-grow 属性的值和 flex-basis 属性设置的基准宽度确定

请看图 17-13,图中有一个弹性容器,容器里有三个弹性项目。因为各个项目的 flex-grow 属性值大于 0,所以这些项目会变宽,占满整个容器的宽度(1000 像素)。
这三个项目的 flex-basis 属性值各不相同,分别为 300、200 和 100 像素。首先,计算最小宽度之和:

1
300 + 200 + 100 = 600

600 这个值是三个项目想占据的宽度。可是,容器的宽度(1000 像素)比这大。因此,我们可以拿容器的宽度减项目的宽度之和,得到剩余宽度:

1
1000 - 600 = 400

也就是说,浏览器要确定怎么处理这 400 像素的额外空间。此时,浏览器会查看各个项目的 flex-grow 属性值(1、3 和 4),然后决定怎么分配剩余的空间。
1 + 3 + 4 = 8,因此第一个项目会得到剩余空间的 1/8。400 的1/8即(400/8)是50,因此第一个项目的宽度是 flex-basis 属性的值加上从剩余空间中分到的量:

1
300 + 50 = 350像素

第二个项目会得到剩余空间的3/8,然后加上 flex-basis 属性的值

1
300 + (50 * 3) = 350像素

最后一个项目获得剩余 400 像素的 1/2,所以它的宽度是:

1
100 + 200 = 300 像素

计算方式有多种,可以看出,浏览器要做很多工作才能确定弹性项目的宽度。

回到 flex-shrink 属性

flex 属性的第二个值用于确定项目的宽度之和大于容器的宽度时,弹性项目收窄多少。只有容器不换行(nowrap),这个值才有意义,因此,所有项目必须并排显示在一行里

例如,在图 17-14 里的示例中,弹性容器的宽度是 1000 像素。因为各个项目的 flex-basis 属性值是 400 像素(宽度之和等于 1200 像素),所以如果项目不收窄,在容器里放不下。
在第一行里,各个项目的 flex-shink 属性值都是 1,因此每个项目都会收窄(减少宽度)相同的量

图17-14flex-shrink

图17-14 如果 flex-shrink 属性的值是 0,容器的换行方式设为 nowrap,那么行里的弹性项目会超出容器的边界(底部)。我们可能不希望出现这种状况

理解 flex 属性的默认值:
如果不为弹性容器里的弹性项目设置 flex 属性,浏览器会提供默认值,如下所示:

1
flex: 0 1 auto;

此时,弹性项目的宽度由里面的内容自动确定。包含大量文本和多个图片的弹性项目,比只有两个单词的项目要宽很多。

可是,设置 flex 属性的某个值后,浏览器会为其他值提供不同的默认值。flex 属性其实是三个属性的简写形式: flex-growflex-shrinkflex-basis

因此,flex: 1 1 400px; 这一行代码等同于:

1
2
3
flex-grow: 1;
flex-shrink: 1;
flex-basis: 400px;

如果不为 flex-shrink 和 flex-basis 属性设定值,例如使用 flex: 1;,浏览器会把 flex-shrink 属性的默认值设为 1,把 flex-basis 属性的默认值设为 0%。
也就是说,flex: 1;等同于:

1
2
3
flex-grow: 1;
flex-shrink: 1;
flex-basis: 0%;

这与整个 flex 属性都不设置差别很大。flex-basis 属性的值为 0% 时,弹性项目的宽度完全由 flex-grow 属性决定。也就是说,弹性项目里内容的量对项目的宽度没有影响。

不过,在图 17-14 中间那一行里,第一个项目的 flex-shrink 属性值是 1,其他两个项目的 flex-shrink 属性值是 4,因此各个项目的宽度并不相同。
flex-grow 属性的值越大,项目越宽,但是 flex-shrink 属性用于确定,较之同一行里的其他项目,收窄多少。
这意味着,flex-shrink 属性的值越大,项目宽度允许收窄的量就越多。所以,在图 17-14 中间那一行里,右边两个项目更窄,因为那两个项目的 flex-shrink 属性值更大。

flex-shrink 属性又增加了一层复杂度,让弹性项目的工作方式更难理解。
鉴于此,而且因为弹性容器允许换行时 flex-shrink 属性没有作用,所以可以始终把 flex-shrink 属性的值设为 1.

注意: 只有弹性容器不允许换行显示弹性项目时,flex-shrink 属性才起作用。也就是说,如果把弹性容器的 flex-flow 属性设置为下面这样:

1
2
3
4
.container {
  display: flex;
  felx-flow: row nowrap;
}

容器允许换行,那么,弹性项目在一行里放不下时,会移到下一行(见图 17-15)

换行显示弹性项目

允许弹性容器换行时,flex-basis 属性的值才真正有用:

1
2
3
4
.container {
  display: flex;
  flex-flow: row wrap;
}

允许换行时,如果容器里放不下弹性项目,会新起一行显示。假如有个弹性容器的宽度是 1000 像素,里面有三个弹性项目,而每个项目的 flex-basis 属性值都是 400.
因为400 + 400 + 400大于1000,所以这三个弹性项目在容器里放不下哦。可是,400 + 400小于1000,所以其中两个项目可以并排显示成一行。

此时,浏览器会把前两个项目在一行里,把第三个项目单独显示在一行里(如图 17-15 上部)。因为这三个项目都设置了 flex-grow 属性,因此会占满整行。
因此,单独放在一行里的最后一个项目会拉伸,填满整个容器的宽度。

如果收窄容器,弹性项目都无法并排显示,那么浏览器会把各个弹性项目都单独放在一行里(如图 17-15 下部)

图17-15换行显示弹性项目

图17-15 flex-basis 属性可用于弹性项目设置建议宽度。如果弹性容器的宽度不够,无法并排显示所有弹性项目,就可以这么做。
把 flex-flow 属性设为允许换行,弹性项目在一行里放不下时,会换到下一行(下部)

经验法则

可以看出,使用 flex 属性的三个值时要考虑很多事情。容器的宽度不同时,各个值的处理方式也各不相同,而我们很难记住所有可能的组合用法。
不过,下面这些建议可以为你提供指导,让你知道各个属性应该在何时使用:

  • 把所有弹性项目放在一行里。如果只想创建一行宽度不同的项目,不能允许弹性容器换行,而且只为 flex 属性提供一个值即可。
    比如说我们想在一行里创建两个侧边栏和一个主内容区域,我们想让主内容区域占容器宽度的一半,让两个侧边栏各占容器的 25%。使用下述代码就能做到这一点:
    css .container { display: flex; flex-flow: row nowrap; } .sidebar1 { flex: 1; } .sidebar2 { flex: 1; } .main { flex: 2; } 我们根本不用设置 flex-shrink 和 flex-basis 属性,因为我们只想得到几个宽度比例保持不变的弹性项目
  • 保持一行里各个项目的宽度比例,但是当容器太窄,无法并排显示项目时,允许换行。
    弹性项目的一行里放不下时,如果想让项目换行,那就为容器设置 wrap 选项,并且按照 flex-grow 属性值的比例设置各个弹性项目的 flex-basis 属性
    css .container { display: flex; flex-flow: row wrap; } .sidebar1 { flex: 1 1 100px; } .sidebar2 { flex: 1 1 100px; } .main { flex: 2 1 200px; } 为了让各个弹性项目的宽度之比与 flex-grow 属性的值保持一致,应该按照 flex-grow 属性的比例设置 flex-basis 属性的值。
    例如,在上述代码中,有三个弹性项目,各自的 flex-grow 属性值分别是 1、1 和 2.所以,设置 flex-basis 属性的值时要保持这个比例,如 100px、100px 和 200px。
    具体的值取决于想在浏览器宽度为多少时换行显示弹性项目。对上述代码来说,弹性容器的宽度小于 400 像素时,弹性项目会换行。
    弹性项目的宽度之和小于容器宽度时,flex-grow 属性的值用于确定如何分配剩余的空间。例如,在图 17-16 下部那一行里,虽然中间那个项目项目的 flex-grow 属性的值为 2,但是其宽度不是其他项目的两倍。
    这是因为,它的 flex-basis 属性值与两外两个项目一样,都是 100px。所以,即便那个项目分到的剩余空间比另外两个项目多,但是都会加到 100 像素上,因此宽度与另外两个项目一样
  • flex-basis 属性的值用于确定何时换行。我们可以把 flex-basis 属性的值当作响应式设计中的断点。例如,页面宽度大于 600 像素时如果想并排显示三个弹性项目,可以把各个项目的 flex-basis 属性设为 200px。
    当浏览器宽度小于 600 像素时,前两个项目会并排显示,而第三个项目会显示到下一行。如果浏览器窗口的宽度小于 400 像素,每个项目都会单独显示在一行,得到纵向叠放的三行。

图17-16flex经验法则

图17-16 如果想让各个弹性项目的宽度保持为一定的比例,要让各个 flex-basis 守护星(弹性项目的基准宽度)的比例与 flex-grow 属性的比例一致(上部)。
否则,一行里各个弹性项目的宽度就无法保持比例不变,如图中下部那一行所示

Flexbox 的原则

一切要从我们熟悉的 display 属性开始。给元素添加 display: flex,该元素变成了一个弹性容器(flex container),它的直接子元素变成了弹性子元素(flex item)。
弹性子元素默认是在同一行按照从左到右的顺序并排排列。弹性容器像块元素一样填满可用宽度,但是弹性子元素不一定填满其弹性容器的宽度。
弹性子元素高度相等,该高度由它们的内容决定。

提示:
还可以用 display: inline-flex。它创建了一个弹性容器,行为类似于 inline-block 元素。
它会跟其他行内元素一起流式排列,但不会自动增长到100%的宽度。
内部的弹性子元素跟使用 display: flex 创建的 Flexbox 里的弹性子元素行为一样。
在实际开发时,很少用到 display: inline-flex

之前提到的 display 值,比如 inline、inline-block 等,只会影响到应用了该样式的元素,而Flexbox则不一样。
一个弹性容器能控制内部元素的布局。弹性容器及其子元素可以用图表示。

弹性容器及其子元素

子元素按照主轴线排列,主轴的方向为主起点(左)到主终点(右)。
垂直于主轴的是副轴。方向从副起点(上)到副终点(下)。这些轴的方向可以改变,稍后会介绍。

说明:
因为Flexbox布局是以主轴和副轴为基础来定义的,所以我会使用起点终点来描述轴,而不是左和右,或者上和下。

这些概念(弹性容器、弹性子元素、两条轴)覆盖了Flexbox的大部分信息。在12种新属性发挥作用之前,仅仅使用display: flex就完成了很多工作。为了验证Flexbox的威力,接下来将构建如图5-2所示的网页。


图5-2 使用Flexbox布局实现的网页

我将通过这个网页的结构来介绍 Flexbox 的几种用法。顶部的导航菜单、三个白色盒子以及右下 “$20.00” 的样式都将用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
<!doctype html>
<head>
  <style>
    :root {
      box-sizing: border-box;
    }

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

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

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

    .container {
      max-width: 1080px;
      margin: 0 auto;
    }
  </style>
</head>

<body>
  <div class="container">
    <header>
      <h1>Ink</h1>
    </header>
    <nav>
      <ul class="site-nav">
        <li><a href="/">Home</a></li>
        <li><a href="/features">Features</a></li>
        <li><a href="/pricing">Pricing</a></li>
        <li><a href="/support">Support</a></li>
        <li class="nav-right">
          <a href="/about">About</a>
        </li>
      </ul>
    </nav>
    <main class="flex">
      <div class="column-main tile">
        <h1>Team collaboration done right</h1>
        <p>Thousands of teams from all over the
          world turn to <b>Ink</b> to communicate
          and get things done.</p>
      </div>
      <div class="column-sidebar">
        <div class="tile">
          <form class="login-form">
            <h3>Login</h3>
            <p>
              <label for="username">Username</label>
              <input id="username" type="text"
                name="username"/>
            </p>
            <p>
              <label for="password">Password</label>
              <input id="password" type="password"
                name="password"/>
            </p>
            <button type="submit">Login</button>
          </form>
        </div>
        <div class="tile centered">
          <small>Starting at</small>
          <div class="cost">
            <span class="cost-currency">$</span>
            <span class="cost-dollars">20</span>
            <span class="cost-cents">.00</span>
          </div>
          <a class="cta-button" href="/pricing">
            Sign up
          </a>
        </div>
      </div>
    </main>
  </div>
</body>

创建一个基础的 Flexbox 菜单

本例要实现如图5-3所示的导航菜单。菜单里的大部分菜单项都居左对齐,只有一项居右对齐。


图 5-3 导航菜单, 菜单项用Flexbox布局

要实现这个菜单,需要考虑让哪个元素做弹性容器,这个元素的子元素便会成为弹性子元素。
在本例中,弹性容器应该是无序列表(<ul>)。它的子元素,即列表项(<li>)就是弹性子元素。代码如下所示。

1
2
3
4
5
6
7
8
9
<ul class="site-nav">
    <li><a href="/">Home</a></li>
    <li><a href="/features">Features</a></li>
    <li><a href="/pricing">Pricing</a></li>
    <li><a href="/support">Support</a></li>
    <li class="nav-right">
        <a href="/about">About</a>
    </li>
</ul>

接下来分几步构建这个菜单。首先,给列表加上 dislay: flex
然后覆盖浏览器默认的列表样式和猫头鹰选择器设置的顶部外边距。同时添加颜色。效果如图5-4所示。


图 5-4 设置了颜色和Flexbox属性后的菜单

在HTML标记里,已经给 <ul> 指定了类 site-nav,可以在样式表中用该类作为选择器。添加代码清单5-3到你的样式表中。

代码清单5-3: 将菜单设置为 Flexbox 并添加颜色

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
.site-nav {
    /* 指定 site-nav 为弹性容器,让子元素成为弹性子元素 */
    display: flex;
    /* 去掉浏览器默认的左侧内边距以及列表的项目符号 */
    padding-left: 0;
    background-color: #5f4b44;
    list-style-type: none;
}

.site-nav > li {
    /* 覆盖猫头鹰选择器的顶部外边距 */
    margin-top: 0;
}

.site-nav > li > a {
    background-color: #cc6b5a;
    color: white;
    /* 覆盖浏览器默认的链接文字的下划线样式 */
    text-decoration: none;
}

注意,现在处理的是三个层级的元素:site-nav列表(弹性容器)、列表项(弹性子元素)、内部的锚点标签(链接)。
直接后代组合器(>)确保了只会选中直接子元素。虽然不是特别有必要,毕竟之后也不太可能会给导航菜单添加嵌套的列表,但是保险起见,还是加上

添加内边距和间隔

现在菜单看起来比较“单薄”,加上内边距能让它变“饱满”一些。给容器和菜单链接都加上内边距后,菜单如图5-5所示。


图 5-5 添加了内边距和链接样式后的菜单

如果你还不太熟悉构建这样的菜单(不管用Flexbox还是其他布局方式),那么请注意一下如何实现。
在这个例子里,应当把菜单项内边距加到内部的 <a> 元素上,而不是 <li> 元素上。
因为整个点击区域的外观和行为应当都符合用户对一个菜单链接的预期,而链接的行为来自于 <a> 元素,
所以如果把 <li> 做成一个好看的大按钮,里面只有很小的区域(<a>)可以点击,就不符合用户预期。

如代码清单5-4所示,更新样式表,给菜单填充内边距。

代码清单5-4: 给菜单和链接添加内边距

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
.site-nav {
    display: flex;
    /* 给链接外的菜单容器添加内边距 */
    padding: .5em;
    background-color: #5f4b44;
    list-style-type: none;
    border-radius: .2em;
}

.site-nav > li {
    margin-top: 0;
}

.site-nav > li > a {
    /* 让链接成为块级元素,这样就能撑开父元素的高度 */
    display: block;
    /* 给链接添加内边距 */
    padding: .5em 1em;
    background-color: #cc6b5a;
    color: white;
    text-decoration: none;
}

注意这里的链接被设置为块级元素。
如果链接还是行内元素,那么它给父元素贡献的高度会根据行高计算,而不是根据内边距和内容,这样不符合预期。
另外,这里给水平方向设置的内边距比垂直方向的要多一点,因为从美学上来讲这样更让人愉悦。

接下来,给菜单项添加间隔。常规的外边距就能做到这一点。
更棒的是,Flexbox允许使用 margin: auto 来填充弹性子元素之间的可用空间。
Flexbox还允许将最后的菜单项移动到右侧。加上外边距后,菜单就完成了(如图5-6所示)。


图 5-6 外边距给弹性子元素加上了间隔

代码清单5-5的样式给每个弹性子元素之间加上了外边距,最外侧的两个元素除外。
为了实现这一效果,可以采用 margin-left 属性和一个相邻兄弟组合器,这个方法跟猫头鹰选择器类似。
同时给最后的按钮加上一个 auto 的左侧外边距,这样就会让这个外边距填充所有可用空间,如此一来,最后的按钮就会被推到最右侧。
添加代码清单5-5到你的样式表。

代码清单 5-5: 使用外边距给弹性子元素加上间隔

1
2
3
4
5
6
7
8
9
/* 选中所有前面有列表项的列表项(也就是说除了第一项之外的所有列表项) */
.site-nav > li + li {
    margin-left: 1.5em;
}

.site-nav > .nav-right {
    /* 弹性盒子内的 auto 外边距会填充所有可用空间 */
    margin-left: auto;
}

代码清单5-5只给一个元素(About)加了 auto 外边距。
如果希望将Support菜单项和About菜单项都推到右侧,则可以把auto外边距加到Support菜单项上。

外边距非常适合现在的场景,因为我们需要菜单项之间的间距不同。
如果希望菜单项等间距,那么 justify-content 属性会是更好的方式。稍后会介绍这个属性。

弹性子元素的大小

前面的代码使用外边距给弹性子元素设置了间距。
你可以用width和height属性设置它们大小,但是比起margin、width、height这些常见属性,Flexbox提供了更多更强大的选项。
我们来看一个更有用的 Flexbox 属性:flex。

flex属性控制弹性子元素在主轴方向上的大小(在这里指的元素的宽度)。
代码清单5-6将给网页的主区域应用弹性布局,并使用flex属性控制每一列的大小。产生的主区域效果如图5-7所示。


图 5-7 应用了弹性布局的主区域

将代码清单5-6的样式添加到样式表中。
代码清单5-6通过 tile 类给三个板块加了白色背景,同时,通过 flex 类给 <main> 元素加了弹性布局。

代码清单5-6: 将主容器设置为Flexbox

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
/* 给三个板块加上白色背景和内边距 */
.tile {
    padding: 1.5em;
    background-color: #fff;
}

/* 将主容器设置为 Flexbox */
.flex {
    display: flex;
}

/* 去掉顶部外边距,给每个弹性子元素之间加上间隔 */
.flex > * + * {
    margin-top: 0;
    margin-left: 1.5em;
}

现在内容已经分为了两列:左侧较大的区域是网页的主要内容,右侧是登录表单和一个小的价格盒子。
因为目前还没有特别设置两列的宽度,所以是根据内容自适应的宽度。
在我的屏幕上(如图5-7所示),它们没有完全填满可用空间,尽管在更小的窗口下可能不会出现这种状况。

说明
在CSS里,不仅要考虑当前网页的内容,还要考虑内容变化后的情况,或者是相同的样式表作用到相似网页上的情况。
要考虑清楚在不同情况下,页面元素(比如这两列)的表现是什么样的。

弹性子元素的flex属性其实包含了好几个选项。我们先通过最基础的用例熟悉一下这个属性。
这里用 column-maincolumn-sidebar 类来指定两列,使用 flex 属性给两列分别赋以 2/3 和 1/3 的宽度。
将代码清单5-7添加到样式表中。

代码清单5-7 使用flex属性设置列宽

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
.column-main {
    flex: 2;
}

.column-sidebar {
    flex: 1;
}
flex-grow: 2;
flex-shrink: 1;
flex-basis: 0%;

现在两列扩展宽度填满空间,它们的宽度加起来等于nav导航条的宽度,同时左侧主区域的宽度是侧边栏宽度的两倍。
Flexbox 已贴心地完成了所有数学计算。来看一下它的具体计算过程。

flex 属性是三个不同大小属性的简写:flex-growflex-shrinkflex-basis
在代码清单5-7里,只提供了 flex-grow 的值,剩下的两个属性是默认值(分别是 1 和 0%),因此 flex: 2 等价于flex: 210%。通常首选简写属性,但也可以分别声明三个属性。

接下来看看三个属性分别表示什么。
先从 flex-basis 开始,因为其余两个属性都基于 flex-basis

使用 flex-basis 属性

flex-basis 定义了元素大小的基准值,即一个初始的“主尺寸”。
flex-basis 属性可以设置为任意的 width 值,包括 px、em、百分比。
它的初始值是 auto,此时浏览器会检查元素是否设置了 width 属性值。
如果有,则使用 width 的值作为 flex-basis 的值;如果没有,则用元素内容自身的大小。
如果 flex-basis 的值不是 auto, width 属性会被忽略。如图5-8所示。


图5-8 三个弹性子元素的弹性基准是20%,每一个元素初始主尺寸(宽度)为20%

每个弹性子元素的初始主尺寸确定后,它们可能需要在主轴方向扩大或者缩小来适应(或者填充)弹性容器的大小。
这时候就需要 flex-growflex-shrink 来决定缩放的规则。

使用 flex-grow 属性

每个弹性子元素的flex-basis值计算出来后,它们(加上子元素之间的外边距)加起来会占据一定的宽度。
加起来的宽度不一定正好填满弹性容器的宽度,可能会有留白(如图5-8所示)。

多出来的留白(或剩余宽度)会按照 flex-grow(增长因子)的值分配给每个弹性子元素,flex-grow 的值为非负整数。
如果一个弹性子元素的 flex-grow 值为0,那么它的宽度不会超过 flex-basis 的值;
如果某个弹性子元素的增长因子非0,那么这些元素会增长到所有的剩余空间被分配完,也就意味着弹性子元素会填满容器的宽度(如图5-9所示)。


图5-9 拥有相同的flex-grow值的子元素会分配相同比例的剩余宽度

flex-grow 的值越大,元素的“权重”越高,也就会占据更大的剩余宽度。
一个 flex-grow: 2 的子元素增长的宽度为 flex-grow: 1 的子元素的两倍(如图5-10所示)。


图5-10 flex-grow值越大,子元素能分配剩余可用宽度的比例越大

还记得前面的三个板块吗?简写声明 flex: 2flex: 1 设置了一个弹性基准值为0 %,因此容器宽度的100%都是剩余宽度(减去两列之间1.5em的外边距)。
剩余宽度会分配给两列:第一列得到 2/3 的宽度,第二列得到 1/3 的宽度(如图5-11所示)。


图5-11 这两列填充了弹性容器的宽度

提示
推荐使用简写属性flex,而不是分别声明 flex-growflex-shrinkflex-basis
与大部分简写属性不一样,如果在flex中忽略某个子属性,那么子属性的值并不会被置为初始值。
相反,如果某个子属性被省略,那么 flex 简写属性会给出有用的默认值:flex-grow 为 1、flex-shrink 为1、flex-basis 为 0%。
这些默认值正是大多数情况下所需要的值。

使用 flex-shrink 属性

flex-shrink属性与flex-grow遵循相似的原则。
计算出弹性子元素的初始主尺寸后,它们的累加值可能会超出弹性容器的可用宽度。
如果不用 flex-shrink,就会导致溢出(如图5-12所示)。


图5-12 弹性子元素的初始大小可能超出弹性容器

每个子元素的 flex-shrink 值代表了它是否应该收缩以防止溢出。
如果某个子元素为 flex-shrink: 0,则不会收缩;如果值大于0,则会收缩至不再溢出。
按照 flex-shrink 值的比例,值越大的元素收缩得越多。

用flex-shrink也能实现上述页面中两列的宽度。首先将两列的flex-basis指定为理想的比例(66.67%和33.33%)。
它们的宽度之和加上1.5em的间隔就会比容器宽度多出1.5em。
然后将两列的flex-shrink设置为1,这样就会从每列的宽度减掉0.75em,于是容器就能容纳两列了。代码如代码清单5-8所示。

代码清单5-8 使用flex属性设置宽度

1
2
3
4
5
6
7
8
9
.column-main {
    /* 等价于 flex: 1 1 66.67%; */
    flex: 66.67%;
}

.column-sidebar {
    /* 等价于 flex: 1 1 33.33% */
    flex: 33.33%;
}

这种解决方案跟前面(代码清单5-7)得到的结果一样,两者都能满足该页面的需求。

说明:
如果看一下这两种方式的细节,就会发现代码清单5-7和代码清单5-8之间有细微的差别。
原因很复杂,但简单来讲,是因为 column-main 有内边距,而 column-sidebar 没有。
flex-basis 为 0% 时,内边距会改变弹性子元素的初始主宽度计算的方式。
因此代码清单5-7的 column-main 比代码清单5-8的要宽 3em,即左右内边距的大小。
如果想要精确的结果,那么要么保证两列有相同的内边距,要么用代码清单5-8的方式设置弹性基准值。

实际应用

flex属性有很多用法。
可以像前面的网页那样,用 flex-grow 值或者 flex-basis 百分比定义每列的比例。
也可以用于定义固定宽度的列和随着视口缩放的“流动”列。
还可以用 Flexbox 而不是浮动构建网格系统。图5-13展示了可以用Flexbox实现的几种布局。


图5-13 用弹性盒子定义元素大小的几种方式

其中第3个例子展示的是“圣杯”布局。
众所周知,用CSS实现这种布局非常困难。
该布局中,两个侧边栏宽度固定,而中间的列是“流动的”,即它会自动填充可用空间。
重点是,三列的高度相等,该高度取决于它们的内容。尽管浮动也能实现这种布局,但需要用一些既晦涩又脆弱的技巧。
你可以使用不同的弹性子元素,想出很多不同的方式来组合以上的布局。

弹性方向

Flexbox 的另一个重要功能是能够切换主副轴方向,用弹性容器的 flex-direction 属性控制。
如前面的例子所示,它的初始值(row)控制子元素按从左到右的方向排列;指定 flex-direction: column 能控制弹性子元素沿垂直方向排列(从上到下)。
Flexbox 还支持 row-reverse 让元素从右到左排列,column-reverse 让元素从下到上排列(如图5-14所示)。


图5-14 改变弹性方向就改变了主轴方向,副轴因为要与主轴垂直,所以方向也随之改变

在示例网页里,右侧的列将用到 flex-direction 属性让右侧的两个板块按从上到下的顺序排列。
这样做似乎多此一举,毕竟右侧的两个板块已经是这样的顺序了。
普通的块级元素本来就会这样排列,但是这种布局有一个隐藏的缺陷,给主板块添加更多内容就能看到问题,如图5-15所示。


图5-15 主板块的高度超过了右侧两个板块的高度(虚线标出了column-sidebar的大小)

给column-main添加一些标题和段落,就会发现主板块超出了右边板块的底部。Flexbox应该能让两列的高度相等,为什么不起作用了呢?

如图5-15(虚线区域)所示,其实左右两个弹性子元素是等高的。
问题是右边栏内部的两个板块没有扩展到填满右边栏区域。
理想的布局应该如图5-16所示。右侧两个板块会扩展高度填满右边栏,尽管左边的内容更长。
在Flexbox之前,纯CSS是无法实现的(需要稍微借助JavaScript)。


图5-16 理想的布局:右边栏的板块跟左边的大板块对齐

改变弹性方向

上述场景的真正需求是让两列扩展到填满容器的高度。
因此要将右边栏(column-sidebar)改为弹性容器,并设置 flex-direction: column
然后给里面的两个板块设置非0的 flex-grow 值。按代码清单5-9所示,更新你的样式表。

代码清单5-9 在右侧创建一个弹性列

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
/* 对外面的弹性盒子来说是弹性子元素,对内部的元素而言是弹性容器 */
.column-sidebar {
    flex: 1;
    display: flex;
    flex-direction: column;
}

.column-sidebar > .tile {
    /* 给内部子元素加上 flex-grow */
    flex: 1;
}

以上代码创建了一个嵌套的弹性盒子。
对外层的弹性盒子来说,<div class="column-sidebar"> 是弹性子元素,对内部的弹性盒子来说,它就是弹性容器。
整体结构如下所示(简洁起见,省略了文字)。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
<main class="flex">
    <div class="column-main tile">
        ...
    </div>
    <div class="column-sidebar">
        <div class="tile">
            ...
        </div>
        <div class="tile centered">
            ...
        </div>
    </div>
</main>

内部的弹性盒子的弹性方向为column,因此主轴发生了旋转,现在变成了从上到下(副轴变成了从左到右)。
也就是对于弹性子元素而言,flex-basis、flex-grow和flex-shrink现在作用于元素的高度而不是宽度。
由于指定了flex: 1,因此在必要的时候子元素的高度会扩展到填满容器。
无论哪边更高,主板块的底部和右边第二个小板块的底部都会对齐。

水平弹性盒子的大部分概念同样适用于垂直的弹性盒子(column或column-reverse),但是有一点不同:在CSS中处理高度的方式与处理宽度的方式在本质上不一样。
弹性容器会占据100%的可用宽度,而高度则由自身的内容来决定。
即使改变主轴方向,也不会影响这一本质。

弹性容器的高度由弹性子元素决定,它们会正好填满容器。
在垂直的弹性盒子里,子元素的 flex-growflex-shrink 不会起作用,除非有“外力”强行改变弹性容器的高度。
在网页里,“外力”就是从外层弹性盒子计算出来的高度。

登录表单的样式

网页的整体布局已经完成了。剩下的工作是给右侧两个板块里的较小的元素添加样式,主要是登录表单和注册链接。
登录表单不需要使用弹性盒子了,但是为了示例的完整性,我简单说明一下。最终完成的样式会如图5-17所示。


图5-17 登录表单

<form> 的类名为 login-form,在CSS中用作表单的选择器。
将代码清单5-10添加到你的样式表里。分别给登录表单的标题、输入区域、按钮布局。

代码清单5-10 登录表单的样式

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/* 标题设置为加粗、右对齐、全大写 */
.login-form h3 {
    margin: 0;
    font-size: .9em;
    font-weight: bold;
    text-align: right;
    text-transform: uppercase;
}

/* 给文本类型的输入框(不包含复选框和单选按钮)添加样式 */
.login-form input:not([type=checkbox]):not([type=radio]) {
    display: block;
    margin-top: 0;
    width: 100%;
}

/* 给按钮添加样式 */
.login-form button {
    margin-top: 1em;
    border: 1px solid #cc6b5a;
    background-color: white;
    padding: .5em 1em;
    cursor: pointer;
}

标题使用了一些我们熟知的字体属性。text-align 将文字放到右侧,text-transform 让文字全大写。
注意,在HTML里面并没有全大写。当大写仅仅是一种样式时,正常的做法是在HTML中按标准的语法规则书写,再用CSS实现大写。
这样将来不用重新输入HTML中的文字就可以改变大小写格式。

第二组规则给输入框加上了样式。这里的选择器比较特殊,主要是因为 <input> 元素很特殊。
<input> 元素可以是文本和密码输入框以及很多类似的HTML5输入框,比如数字、邮箱、日期输入框。
它还可以是看起来完全不一样的输入元素,即单选按钮和复选框。

这里结合了 :not() 伪类和属性选择器 [type=checkbox] 以及 [type=radio],可以选中除了复选框和单选按钮以外的所有 <input>元素。
这是一个黑名单方式:把不想选中的元素排除掉。
你也可以采用白名单方式:使用多个属性选择器将想要选中的所有 <input> 类型都列出来,但这样一来代码就很长了。

说明
虽然该网页的表单只使用了文本和密码输入框,但是我们需要考虑到这段CSS在未来可能影响的其他标记,并尽量照顾到那些标记。

在这组规则里,给输入框设置了 display: block,让它们单独占据一行,还要将其宽度设置为100%。
通常情况下,块级元素会自动填满可用宽度,但是 <input> 比较特殊,其宽度由size属性决定,而它表示不出滚动条的情况下大致能容纳的字符数量。
如果不指定的话,该属性就会恢复为默认值。可以用CSS的width属性强制指定宽度。

第三组规则设置了Login按钮的样式。
这些样式大多数很简单,只有cursor属性相对比较陌生,它控制鼠标指针移到元素上时显示的样式。
它的值为pointer时,鼠标指针会变成带一个指示手指的手型,鼠标悬停到链接元素的默认鼠标指针就是这个形状。
这种形状告诉用户元素是可以点击的。这个细节能够让按钮更完美。

对齐、间距等细节

现在你已牢牢掌握了Flexbox最核心的属性,不过前面提到过,还有很多选项可以偶尔派上用场。
这些选项大多数跟弹性容器内弹性子元素的对齐或者间距相关。还有一些选项可以设置换行、重新给子元素单独排序。
这些控制属性都列在了接下来的几页里:表5-1列出了弹性容器的所有属性,表5-2列出了弹性子元素的所有属性。

表5-1 弹性容器的属性

表5-2 弹性子元素的属性

通常情况下,创建一个弹性盒子需要用到前面提及的这些方法。

  • 选择一个容器及其子元素,给容器设置display: flex
  • 如有必要,给容器设置flex-direction
  • 给弹性子元素设置外边距和/或flex值,用来控制它们的大小

将元素大致摆放到合适的位置后,就可以按需添加其他的Flexbox属性了。
建议先熟悉到目前为止介绍的概念,然后继续读完剩下的内容,了解其他属性,等用得着的时候再记住这些属性。
当你发现自己需要用到这些属性时,再回头参考这一节。虽然剩下的大部分属性相当简单,但是使用频率不高。

理解弹性容器的属性

弹性容器上有好几个属性可以控制弹性子元素的布局。首先是介绍过的 flex-direction,下面来看看其他属性。

flex-wrap 属性

flex-wrap属性允许弹性子元素换到新的一行或多行显示。
它可以设置为nowrap(初始值)、wrap或者wrap-reverse。启用换行后,子元素不再根据flex-shrink值收缩,任何超过弹性容器的子元素都会换行显示。

如果弹性方向是 column 或 column-reverse,那么 flex-wrap 会允许弹性子元素换到新的一列显示,不过这只在限制了容器高度的情况下才会发生,否则容器会扩展高度以包含全部弹性子元素。

flex-flow 属性

flex-flow 属性是 flex-directionflex-wrap 的简写。
例如,flex-grow: column wrap 指定弹性子元素按照从上到下的方式排列,必要时换到新的一列。

justify-content 属性

当子元素未填满容器时,justify-content属性控制子元素沿主轴方向的间距。
它的值包括几个关键字:flex-start、flex-end、center、space-between以及space-around。
默认值flex-start让子元素从主轴的开始位置顺序排列,比如主轴方向为从左到右的话,开始位置就是左边。
如果不设置外边距,那么子元素之间不会产生间距。
如果值为flex-end,子元素就从主轴的结束位置开始排列,center的话则让子元素居中。

值space-between将第一个弹性子元素放在主轴开始的地方,最后一个子元素放在主轴结束的地方,剩下的子元素间隔均匀地放在这两者之间的区域。
值space-around类似,只不过给第一个子元素的前面和最后一个子元素的后面也加上了相同的间距。

间距是在元素的外边距之后进行计算的,而且flex-grow的值要考虑进来。
也就是说,如果任意子元素的 flex-grow 的值不为0,或者任意子元素在主轴方向的外边距值为 auto, justify-content 就失效了。

align-items 属性

justify-content控制子元素在主轴方向的对齐方式,align-items则控制子元素在副轴方向的对齐方式。
align-items的初始值为stretch,在水平排列的情况下让所有子元素填充容器的高度,在垂直排列的情况下让子元素填充容器的宽度,因此它能实现等高列。

其他的值让弹性子元素可以保留自身的大小,而不是填充容器的大小。(类似的概念有vertical-align属性。)

  • flex-start 和 flex-end 让子元素与副轴的开始或结束位置对齐。(如果是水平布局的话,则与容器的顶部或者底部分别对齐。)
  • center 让元素居中。
  • baseline 让元素根据每个弹性子元素的第一行文字的基线对齐。

当你想要一个弹性子元素里大字号标题的基线与其他弹性子元素里较小文字的基线对齐时,baseline就能派上用场。

提示:
justify-content和align-items属性的名称很容易弄混。
我是参考文字样式来记的:我们可以“调整”(justify)文字,让其在水平方向的两端之间均匀分布;
而align-items更像vertical-align,让行内元素在垂直方向“对齐”(align)。

align-content 属性

如果开启了换行(用 flex-wrap), align-content 属性就可以控制弹性容器内沿副轴方向每行之间的间距。
它支持的值有 flex-start、flex-end、center、stretch(初始值)、space-between 以及 space-around。
这些值对间距的处理类似上面的 justify-content

理解弹性子元素的属性

前面已经介绍了弹性子元素的 flex-growflex-shrinkflex-basis 以及它们的简写属性flex。
接下来再介绍两个弹性子元素的属性:align-selforder

align-self 属性

该属性控制弹性子元素沿着容器副轴方向的对齐方式。
它跟弹性容器的align-items属性效果相同,但是它能单独给弹性子元素设定不同的对齐方式。
auto为初始值,会以容器的align-items值为准。其他值会覆盖容器的设置。
align-self 属性支持的关键字与 align-items 一样:flex-start、flex-end、center、stretch 以及 baseline。

order 属性

正常情况下,弹性子元素按照在HTML源码中出现的顺序排列。它们沿着主轴方向,从主轴的起点开始排列。
使用order属性能改变子元素排列的顺序。还可以将其指定为任意正负整数。
如果多个弹性子元素有一样的值,它们就会按照源码顺序出现。

初始状态下,所有的弹性子元素的order都为0。指定一个元素的值为−1,它会移动到列表的最前面;
指定为1,则会移动到最后。可以按照需要给每个子元素指定order以便重新编排它们。这些值不一定要连续。

警告
谨慎使用order。让屏幕上的视觉布局顺序和源码顺序差别太大会影响网站的可访问性。
在大多数浏览器里使用Tab键浏览元素的顺序与源码保持一致,如果视觉上差别太大就会令人困惑。
视力受损的用户使用的大部分屏幕阅读器也是根据源码的顺序来的。

使用对齐属性

我们使用以上介绍的一些属性来完成本章的网页。
最后一个板块有一个带样式的价格和一个行动召唤(call-to-action, CTA)按钮。完成后的效果如图5-18所示。


图5-18 使用Flexbox实现的文字样式

这一块的HTML标记已在网页里写好了,如下所示。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<div class="tile centered">
    <small>Starting at</small>
    <div class="cost">
    <span class="cost-currency">$</span>
    <span class="cost-dollars">20</span>
    <span class="cost-cents">.00</span>
    </div>
    <a class="cta-button" href="/pricing">
    Sign up
    </a>
</div>

文字 $20.00 包在 <div class="cost"> 中,该元素将作为弹性容器。
它有三个弹性子元素,放置三个需要对齐的文字部分($20.00)。
这里用span而不是div来放置文字,因为span默认就是行内元素。
如果因为某些原因CSS加载失败,或者浏览器不支持 Flexbox,那么 $20.00 仍然会在一行显示。

下面的代码清单里,使用 justify-content 让弹性子元素在弹性容器里水平居中,然后用 align-itemsalign-self 控制文字的垂直对齐。将代码清单5-11添加到样式表。

代码清单5-11 给价格板块设置样式

 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
.centered {
    text-align: center;
}

.cost {
    display: flex;
    /* 让弹性子元素在主轴和副轴方向上均居中 */
    justify-content: center;
    align-items: center;
    line-height: .7;
}

.cost > span {
    /* 覆盖猫头鹰选择器设置的外边距 */
    margin-top: 0;
}

/* 给价格的各个部分设置不同的字号 */
.cost-currency {
    font-size: 2rem;
}
.cost-dollars {
    font-size: 4rem;
}
.cost-cents {
    font-size: 1.5rem;
    /* 覆盖这个子元素的 align-items,将其与容器顶部而不是中间对齐 */
    align-self: flex-start;
}

.cta-button {
    display: block;
    background-color: #cc6b5a;
    color: white;
    padding: .5em 1em;
    text-decoration: none;
}

以上代码清单给带样式的 $20.00 设置了Flexbox布局,同时定义了centered类让剩下的文字居中,还给CTA按钮定义了cta-button类实现按钮样式。

代码清单里有一个比较特殊的声明:line-height: .7,这是因为每个弹性子元素的文字行高决定了每个子元素的高度,也就是说元素的高度比文字本身的高度多一些。
因为1em的高度包含了下伸部,而这里的文字刚好没有,所以字符实际上比1em要矮。
我反复试验,直到20的顶部和.00在视觉上对齐,才得到这个值。

值得注意的地方

Flexbox的实现是CSS的一大进步。
一旦你熟悉了它,你可能想要在页面的每个地方都开始使用,不过你应该依靠正常的文档流,只在必要的时候才使用Flexbox。
这么说并不是让你不用它,而是希望你不要拿着锤子满世界找钉子。

Flexbugs

并不是所有浏览器都完美地实现了Flexbox,尤其是IE10和IE11。
Flexbox在大多数情况下可以正常工作,但是可能会在一些环境下遇到bug。一定要确保在你想要支持的旧版浏览器上充分测试它。

与其花费时间讨论你可能或者永远不会遇到的bug,我更愿意推荐一个特别棒的资源,叫Flexbugs。
它的GitHub页面维护了所有已知的Flexbox的浏览器bug(本书写作时总共有14个),解释了哪些环境下会导致这些bug,并大部分情况下给出了解决方案。如
果你发现在某个浏览器下Flexbox布局表现得不太一样,请访问这个页面看看是不是遇到了其中的浏览器bug。

整页布局

Flexbox的一个有趣之处在于如何基于弹性子元素的数量和其中的内容量(及大小)来计算容器的大小。
因为如果网页很大,或者加载很慢时可能会产生奇怪的行为。

当浏览器加载内容时,它渐进渲染到了屏幕,即使此时网页的剩余内容还在加载。
假设有一个使用弹性盒子(flex-direction: row)实现的三列布局。
如果其中两列的内容加载了,浏览器可能会在加载完第三列之前就渲染这两列。
然后等到剩余内容加载完,浏览器会重新计算每个弹性子元素的大小,重新渲染网页。
用户会短暂地看到两列布局,然后列的大小改变(可能改变特别大),并出现第三列。

Jake Archibald是Google Chrome的技术推广工程师,他写过一篇文章Don't use fexbox for overall page layout讨论这个问题。你可以在这篇文章里看到前面所说的情况。他给出的一个建议是对整页布局的时候使用网格布局

说明:
只有一行多列的布局才会产生这个问题。如果主页面布局采用的是一列多行(flex-direction: column),就不会出现以上问题。

总结

  • 使用 Flexbox 实现灵活易操作的网页内容布局。
  • Autoprefixer可以简化 Flexbox 对旧版浏览器的支持。
  • 使用 flex 指定任何能想到的弹性子元素大小的组合。
  • 使用嵌套的弹性盒子来组成复杂的布局,以及填满自适应大小的盒子的高度。
  • Flexbox 自动地创建等高的列。
  • 使用 align-itemsalign-self 让一个弹性子元素在弹性容器中垂直居中。

教程:使用弹性盒构建布局

 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
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>CSS: The Missing Manual</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link href="https://fonts.googleapis.com/css?family=Raleway:400,300,600" rel="stylesheet" type="text/css">
  <link rel="stylesheet" href="css/base.css">
  <link rel="stylesheet" href="css/custom.css">
</head>
<body>
<!-- top section -->
<div class="header">
  <div class="section">
    <div class="nav">
      <a class="button button-primary" href="#">Home</a>
      <a class="button" href="#">Our Clients</a>
      <a class="button" href="#">About Us</a>
      <a class="button" href="#">Careers</a>
    </div>
    <div class="action">
      <h1>We do great things</h1>
      <h2>Donec pulvinar ullamcorper metus</h2>
      <a href="#" class="button button-primary">Learn More</a>
    </div>
  </div>
</div>
<!-- info section -->
<div class="info">
  <div class="section">
      <h2>Graeco feugiat intellegam at vix</h2>
  </div>
  <div class="section boxes">
    <div>
      <p>Mei te possit instructior, idque delenit qui et, dicit ludus commune vix ad. Ea modo dicam cetero mel, case disputationi at est. Ad nam adipisci dignissim posidonium, eius reque temporibus ne mea. Vero antiopam ea vim. Ei timeam electram vix. Eam at tollit erroribus, ea eum idque legere epicuri. Sed quot legere minimum cu, ex mei fastidii mandamus intellegam, error consul ut per.</p>
      <p class="more"><a href="#" class="button">More info</a></p>
    </div>
    <div>
      <p>Mei te possit instructior, idque delenit qui et, dicit ludus commune vix ad. Ea modo dicam cetero mel, case disputationi at est. Ad nam adipisci dignissim posidonium, eius reque temporibus ne mea. Vero antiopam ea vim. Ei timeam electram vix. Eam at tollit erroribus, ea eum idque legere epicuri. </p>
      <p class="more"><a href="#" class="button">More info</a></p>
    </div>
    <div>
      <p>Mei te possit instructior, idque delenit qui et, dicit ludus commune vix ad. Ea modo dicam cetero mel, case disputationi at est. Ad nam adipisci dignissim posidonium, eius reque temporibus ne mea. Vero antiopam ea vim. Ei timeam electram vix. Eam at tollit erroribus, ea eum idque legere epicuri. Sed quot legere minimum cu, ex mei fastidii mandamus intellegam, error consul ut per. Sed quot legere minimum cu, ex mei fastidii mandamus intellegam, error consul ut per.</p>
      <p class="more"><a href="#" class="button">More info</a></p>
    </div>
  </div>
</div>
<!-- footer -->
<div class="footer section">
  <div class="copyright">
    <p>© My Company, 2015. My Company is a registered trademark of His Company, which is a wholly owned subsidiary of Her Company. Any similarity to existing web sites is purely coincidence.</p>
  </div>
  <div class="signup">
    <form class="container">
      <label for="exampleEmailInput">Sign up for our newsletter</label>
      <input type="email" placeholder="test@mailbox.com" id="exampleEmailInput">
      <input class="button-primary" type="submit" value="Submit">
    </form>
  </div>
</div>
</body>
</html>

custom.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
.nav, .boxes, .footer, .footer form{
  display: -webkit-flex;
  display: flex;
  -webkit-flex-flow: row wrap;
  flex-flow: row wrap;
}
.nav {
  -webkit-justify-content: space-between;
  justify-content: space-between;
}
.nav a {
  width: 23%;
}
.boxes div {
  -webkit-flex: 1 1 250px;
  flex: 1 1 250px;
  margin: 10px;
  border-radius: 5px;
  padding: 10px 10px 0 10px;
  background-color: rgba(0,0,0,.1);
  display: -webkit-flex;
  display: flex;
  -webkit-flex-flow: column;
  flex-flow: column;
}
.boxes .more {
  margin-top: auto;
}
.footer .copyright {
  -webkit-flex: 2 1 500px;
  flex: 2 1 500px;
  margin-right: 30px;
}
.footer .signup {
  -webkit-flex: 1 1 250px;
  flex: 1 1 250px;
}
.signup label {
  width: 100%;
}
.signup input[type="email"] {
  border-radius: 4px 0 0 4px;
  -webkit-flex: 1;
  flex: 1;
}
.signup input[type="submit"] {
  border-radius: 0 4px 4px 0;
  padding: 0 10px;
}
@media (max-width: 500px) {
  .nav {
    -webkit-flex-flow: column;
    flex-flow: column;
  }
  .nav a {
    width: 100%;
    margin-bottom: 2px;
  }
}

使用弹性盒布局示例效果

base.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
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
/*! normalize.css v3.0.2 | MIT License | git.io/normalize */

/**
 * 1. Set default font family to sans-serif.
 * 2. Prevent iOS text size adjust after orientation change, without disabling
 *    user zoom.
 */

html {
  font-family: sans-serif; /* 1 */
  -ms-text-size-adjust: 100%; /* 2 */
  -webkit-text-size-adjust: 100%; /* 2 */
}

/**
 * Remove default margin.
 */

body {
  margin: 0;
}

/* HTML5 display definitions
   ========================================================================== */

/**
 * Correct `block` display not defined for any HTML5 element in IE 8/9.
 * Correct `block` display not defined for `details` or `summary` in IE 10/11
 * and Firefox.
 * Correct `block` display not defined for `main` in IE 11.
 */

article,
aside,
details,
figcaption,
figure,
footer,
header,
hgroup,
main,
menu,
nav,
section,
summary {
  display: block;
}

/**
 * 1. Correct `inline-block` display not defined in IE 8/9.
 * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera.
 */

audio,
canvas,
progress,
video {
  display: inline-block; /* 1 */
  vertical-align: baseline; /* 2 */
}

/**
 * Prevent modern browsers from displaying `audio` without controls.
 * Remove excess height in iOS 5 devices.
 */

audio:not([controls]) {
  display: none;
  height: 0;
}

/**
 * Address `[hidden]` styling not present in IE 8/9/10.
 * Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22.
 */

[hidden],
template {
  display: none;
}

/* Links
   ========================================================================== */

/**
 * Remove the gray background color from active links in IE 10.
 */

a {
  background-color: transparent;
}

/**
 * Improve readability when focused and also mouse hovered in all browsers.
 */

a:active,
a:hover {
  outline: 0;
}

/* Text-level semantics
   ========================================================================== */

/**
 * Address styling not present in IE 8/9/10/11, Safari, and Chrome.
 */

abbr[title] {
  border-bottom: 1px dotted;
}

/**
 * Address style set to `bolder` in Firefox 4+, Safari, and Chrome.
 */

b,
strong {
  font-weight: bold;
}

/**
 * Address styling not present in Safari and Chrome.
 */

dfn {
  font-style: italic;
}

/**
 * Address variable `h1` font-size and margin within `section` and `article`
 * contexts in Firefox 4+, Safari, and Chrome.
 */

h1 {
  font-size: 2em;
  margin: 0.67em 0;
}

/**
 * Address styling not present in IE 8/9.
 */

mark {
  background: #ff0;
  color: #000;
}

/**
 * Address inconsistent and variable font size in all browsers.
 */

small {
  font-size: 80%;
}

/**
 * Prevent `sub` and `sup` affecting `line-height` in all browsers.
 */

sub,
sup {
  font-size: 75%;
  line-height: 0;
  position: relative;
  vertical-align: baseline;
}

sup {
  top: -0.5em;
}

sub {
  bottom: -0.25em;
}

/* Embedded content
   ========================================================================== */

/**
 * Remove border when inside `a` element in IE 8/9/10.
 */

img {
  border: 0;
}

/**
 * Correct overflow not hidden in IE 9/10/11.
 */

svg:not(:root) {
  overflow: hidden;
}

/* Grouping content
   ========================================================================== */

/**
 * Address margin not present in IE 8/9 and Safari.
 */

figure {
  margin: 1em 40px;
}

/**
 * Address differences between Firefox and other browsers.
 */

hr {
  -moz-box-sizing: content-box;
  box-sizing: content-box;
  height: 0;
}

/**
 * Contain overflow in all browsers.
 */

pre {
  overflow: auto;
}

/**
 * Address odd `em`-unit font size rendering in all browsers.
 */

code,
kbd,
pre,
samp {
  font-family: monospace, monospace;
  font-size: 1em;
}

/* Forms
   ========================================================================== */

/**
 * Known limitation: by default, Chrome and Safari on OS X allow very limited
 * styling of `select`, unless a `border` property is set.
 */

/**
 * 1. Correct color not being inherited.
 *    Known issue: affects color of disabled elements.
 * 2. Correct font properties not being inherited.
 * 3. Address margins set differently in Firefox 4+, Safari, and Chrome.
 */

button,
input,
optgroup,
select,
textarea {
  color: inherit; /* 1 */
  font: inherit; /* 2 */
  margin: 0; /* 3 */
}

/**
 * Address `overflow` set to `hidden` in IE 8/9/10/11.
 */

button {
  overflow: visible;
}

/**
 * Address inconsistent `text-transform` inheritance for `button` and `select`.
 * All other form control elements do not inherit `text-transform` values.
 * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera.
 * Correct `select` style inheritance in Firefox.
 */

button,
select {
  text-transform: none;
}

/**
 * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
 *    and `video` controls.
 * 2. Correct inability to style clickable `input` types in iOS.
 * 3. Improve usability and consistency of cursor style between image-type
 *    `input` and others.
 */

button,
html input[type="button"], /* 1 */
input[type="reset"],
input[type="submit"] {
  -webkit-appearance: button; /* 2 */
  cursor: pointer; /* 3 */
}

/**
 * Re-set default cursor for disabled elements.
 */

button[disabled],
html input[disabled] {
  cursor: default;
}

/**
 * Remove inner padding and border in Firefox 4+.
 */

button::-moz-focus-inner,
input::-moz-focus-inner {
  border: 0;
  padding: 0;
}

/**
 * Address Firefox 4+ setting `line-height` on `input` using `!important` in
 * the UA stylesheet.
 */

input {
  line-height: normal;
}

/**
 * It's recommended that you don't attempt to style these elements.
 * Firefox's implementation doesn't respect box-sizing, padding, or width.
 *
 * 1. Address box sizing set to `content-box` in IE 8/9/10.
 * 2. Remove excess padding in IE 8/9/10.
 */

input[type="checkbox"],
input[type="radio"] {
  box-sizing: border-box; /* 1 */
  padding: 0; /* 2 */
}

/**
 * Fix the cursor style for Chrome's increment/decrement buttons. For certain
 * `font-size` values of the `input`, it causes the cursor style of the
 * decrement button to change from `default` to `text`.
 */

input[type="number"]::-webkit-inner-spin-button,
input[type="number"]::-webkit-outer-spin-button {
  height: auto;
}

/**
 * 1. Address `appearance` set to `searchfield` in Safari and Chrome.
 * 2. Address `box-sizing` set to `border-box` in Safari and Chrome
 *    (include `-moz` to future-proof).
 */

input[type="search"] {
  -webkit-appearance: textfield; /* 1 */
  -moz-box-sizing: content-box;
  -webkit-box-sizing: content-box; /* 2 */
  box-sizing: content-box;
}

/**
 * Remove inner padding and search cancel button in Safari and Chrome on OS X.
 * Safari (but not Chrome) clips the cancel button when the search input has
 * padding (and `textfield` appearance).
 */

input[type="search"]::-webkit-search-cancel-button,
input[type="search"]::-webkit-search-decoration {
  -webkit-appearance: none;
}

/**
 * Define consistent border, margin, and padding.
 */

fieldset {
  border: 1px solid #c0c0c0;
  margin: 0 2px;
  padding: 0.35em 0.625em 0.75em;
}

/**
 * 1. Correct `color` not being inherited in IE 8/9/10/11.
 * 2. Remove padding so people aren't caught out if they zero out fieldsets.
 */

legend {
  border: 0; /* 1 */
  padding: 0; /* 2 */
}

/**
 * Remove default vertical scrollbar in IE 8/9/10/11.
 */

textarea {
  overflow: auto;
}

/**
 * Don't inherit the `font-weight` (applied by a rule above).
 * NOTE: the default cannot safely be changed in Chrome and Safari on OS X.
 */

optgroup {
  font-weight: bold;
}

/* Tables
   ========================================================================== */

/**
 * Remove most spacing between table cells.
 */

table {
  border-collapse: collapse;
  border-spacing: 0;
}

td,
th {
  padding: 0;
}


* {
  box-sizing: border-box;
}
html {
  font-size: 62.5%; }
body {
  font-size: 1.5em; /* currently ems cause chrome bug misinterpreting rems on body element */
  line-height: 1.6;
  font-weight: 400;
  font-family: "Raleway", "HelveticaNeue", "Helvetica Neue", Helvetica, Arial, sans-serif;
  color: #222;
}

/* Typography
–––––––––––––––––––––––––––––––––––––––––––––––––– */
h1, h2, h3, h4, h5, h6 {
  margin-top: 0;
  margin-bottom: 2rem;
  font-weight: 300; }
h1 { font-size: 4.0rem; line-height: 1.2;  letter-spacing: -.1rem;}
h2 { font-size: 3.6rem; line-height: 1.25; letter-spacing: -.1rem; }
h3 { font-size: 3.0rem; line-height: 1.3;  letter-spacing: -.1rem; }
h4 { font-size: 2.4rem; line-height: 1.35; letter-spacing: -.08rem; }
h5 { font-size: 1.8rem; line-height: 1.5;  letter-spacing: -.05rem; }
h6 { font-size: 1.5rem; line-height: 1.6;  letter-spacing: 0; }

/* Larger than phablet */
@media (min-width: 550px) {
  h1 { font-size: 5.0rem; }
  h2 { font-size: 4.2rem; }
  h3 { font-size: 3.6rem; }
  h4 { font-size: 3.0rem; }
  h5 { font-size: 2.4rem; }
  h6 { font-size: 1.5rem; }
}

p {
  margin-top: 0; }


/* Links
–––––––––––––––––––––––––––––––––––––––––––––––––– */
a {
  color: #1EAEDB; }
a:hover {
  color: #0FA0CE; }


/* Buttons
–––––––––––––––––––––––––––––––––––––––––––––––––– */
.button,
button,
input[type="submit"],
input[type="reset"],
input[type="button"] {
  display: inline-block;
  height: 38px;
  padding: 0 30px;
  color: #555;
  text-align: center;
  font-size: 11px;
  font-weight: 600;
  line-height: 38px;
  letter-spacing: .1rem;
  text-transform: uppercase;
  text-decoration: none;
  white-space: nowrap;
  background-color: transparent;
  border-radius: 4px;
  border: 1px solid #bbb;
  cursor: pointer;
  box-sizing: border-box; }
.button:hover,
button:hover,
input[type="submit"]:hover,
input[type="reset"]:hover,
input[type="button"]:hover,
.button:focus,
button:focus,
input[type="submit"]:focus,
input[type="reset"]:focus,
input[type="button"]:focus {
  color: #333;
  border-color: #888;
  outline: 0; }
.button.button-primary,
button.button-primary,
input[type="submit"].button-primary,
input[type="reset"].button-primary,
input[type="button"].button-primary {
  color: #FFF;
  background-color: #33C3F0;
  border-color: #33C3F0; }
.button.button-primary:hover,
button.button-primary:hover,
input[type="submit"].button-primary:hover,
input[type="reset"].button-primary:hover,
input[type="button"].button-primary:hover,
.button.button-primary:focus,
button.button-primary:focus,
input[type="submit"].button-primary:focus,
input[type="reset"].button-primary:focus,
input[type="button"].button-primary:focus {
  color: #FFF;
  background-color: #1EAEDB;
  border-color: #1EAEDB; }


/* Forms
–––––––––––––––––––––––––––––––––––––––––––––––––– */
input[type="email"],
input[type="number"],
input[type="search"],
input[type="text"],
input[type="tel"],
input[type="url"],
input[type="password"],
textarea,
select {
  height: 38px;
  padding: 6px 10px; /* The 6px vertically centers text on FF, ignored by Webkit */
  background-color: #fff;
  border: 1px solid #D1D1D1;
  border-radius: 4px;
  box-shadow: none;
  box-sizing: border-box; }
/* Removes awkward default styles on some inputs for iOS */
input[type="email"],
input[type="number"],
input[type="search"],
input[type="text"],
input[type="tel"],
input[type="url"],
input[type="password"],
textarea {
  -webkit-appearance: none;
     -moz-appearance: none;
          appearance: none; }
textarea {
  min-height: 65px;
  padding-top: 6px;
  padding-bottom: 6px; }
input[type="email"]:focus,
input[type="number"]:focus,
input[type="search"]:focus,
input[type="text"]:focus,
input[type="tel"]:focus,
input[type="url"]:focus,
input[type="password"]:focus,
textarea:focus,
select:focus {
  border: 1px solid #33C3F0;
  outline: 0; }
label,
legend {
  display: block;
  margin-bottom: .5rem;
  font-weight: 600; }
fieldset {
  padding: 0;
  border-width: 0; }
input[type="checkbox"],
input[type="radio"] {
  display: inline; }
label > .label-body {
  display: inline-block;
  margin-left: .5rem;
  font-weight: normal; }

/* custom styles */

body {
  background-color: black;
}

.header {
  padding: 0;
  background-image: url(../imgs/header.jpg);
  background-size: cover;
}

.section {
  max-width: 960px;
  margin: 0 auto;
  padding: 20px;
}

.nav {
  padding: 10px;
}
.nav a {
  padding-left: 0;
  padding-right: 0;
}
.info {
  background-color: white;
}

.footer {
  color: white;
  padding: 30px;
}



.button {
  background-color: rgba(255,255,255,.5);
  border: none;
}
.button:hover {
  background-color: rgba(255,255,255,.3);
}
.action {
  text-align: center;
  padding-top: 37px;
}
.action h1 {
  margin: 0;
  font-weight: 600;
}
.action h2 {
  margin: 0 0 20px 0;
  font-weight: 600;
}

.info h2 {
  font-size: 3.8rem;
  text-align: center;
}
.info .button {
  background-color: rgb(215, 143, 97);
  border: none;
  color: white;
}

.info .button:hover {
  background-color: rgb(255, 207, 130);
  border: none;
}

.info .section:first-of-type {
  padding-bottom: 0;
}
.info .section:nth-of-type(2) {
  padding-top: 0;
}

form input {
  color: black;
}