介绍
HTML 与 CSS 的结合在很多地方是很棒的内容发布机制 ― 学起来很简单、适用性很高、很强大。然而在文档布局上它还尚未能堪称完美。如果你想要使用只有一两张浮动的图的简单文章布局那倒是没什么关系,但是要弄出复杂的多栏布局就需要既复杂又不可靠的代码,而且还需要为了在不同浏览器上显示一致操劳不断。我们已经滥用浮动与其他构造好长一段时间了,浏览器的错误与渲染差异实在让生活太没趣味了。
为了解决这个困境,CSS3 引入了让布局工作简单很多的几个模块。我们已经在其他文章介绍过多栏布局与分页媒体的生成内容了,现在让我们要来看看伸缩盒布局模块。
所谓的伸缩布局呢,是为了解决之前要布局一串元素的时候实现困难情景。举例来说:
- 在不设置元素的具体宽度的情形下将元素排成横排,并在一排的空间不够的时候折行。
- 快速的将元素排成竖排。
- 将元素沿着父容器的左边、右边或中间排齐。
- 改变元素显示的顺序而不改变 HTML 代码。
- 将每个元素占有的空间设置为父元素宽度/高度的一个比例,却不用担心在父元素的尺寸设置为百分比值的时候或是窗口的尺寸变化的时候布局会坏掉。
听起来很棒吧?让我们来看看详细情形。
这篇文章使用 Opera Next、欧朋 HD 实验室版与 Chrome 支持的最新的伸缩布局语法。注意 Opera 以外的其他浏览器从 2009 开始支持一个已经过时的语法,请真的别再使用了。请注意在读 2012 年以前有关伸缩盒布局的文章的代码的时候注意这点 ― Chris Coyier 有一篇文章是讲到如何判别读到了这种过时文章的。
简单的伸缩例子
让我们先用一个简单的例子来看看伸缩布局有多简单。这里用一个有三个子元素的 “很胖的页脚”,每一个子元素都是很典型的页脚内容,分别是联系方式、重要链接与版权标示。我们要让这些子元素在页面的下面水平排列并置中对齐,又希望重要链接的宽度是另外两个子元素的两倍宽。传统上要弄出这种布局,一般是将给这些子元素设置浮动、宽度,再用各种内边距等等调整对齐。在不设置固定宽度的时候这常常是既复杂又不精确,但是设置固定宽度又让页面不可适应窗口宽度。伸缩布局在这里可以达到这个任务。
注:请打开我的 “很胖的伸缩页脚” 页面来看看完成结果(图 1)― 我用了一个很搞的设计:单栏的主要内容和透过了 position: fixed;
魔法置底的页脚。布局是自适应的 ― 主要内容栏是页面宽度的一个比例宽,而页脚的子元素跟随主要内容栏等比缩放。你会发现我用了几个媒体查询来改变在屏幕小一点的设备上的页面布局。
开始使用伸缩布局
所以,要怎么开始使用伸缩布局呢?大部分的伸缩布局属性都是用在想排列的元素的父容器上的。要将一个容器的内部设置成伸缩布局,你要用 display
属性的一个特殊值,如下:
footer {
display: flex;
}
再来,你要用 flex-flow
属性说你要将子元素排成一个 row
(横排,默认值)或是 column
(竖排)。要让内容在容器在一行里装不下所有伸缩子元素的时候折行,你也可以使用 wrap
关键字。我在我的例子里就是将页脚设成 row wrap
:
footer {
display: flex;
flex-flow: row wrap;
}
wrap
关键字的重要性之后会说明。
注:flex-flow
其实是 flex-direction
(值有 row
、column
、row-reverse
、column-reverse
,后面两个值让横排或是竖排的方向颠倒过来)与 flex-wrap
(值有 wrap
、no-wrap
、wrap-reverse
)的缩写属性。
主轴、侧轴
你在使用伸缩布局的时候你应该熟悉的一个概念是主轴与侧轴,与常说的 X、Y 轴有点像,不过也有点差异。主轴是伸缩布局流向的那个方向的轴,也就是在横排的时候是水平轴,在竖排的时候是垂直轴。请看图 2 的解释。
设置伸缩子元素的对齐方式
伸缩布局的特色是有一系列帮助开发者沿着主轴与侧轴对齐子元素的属性。
沿着侧轴方向对齐项目
我们先来看看让你的伸缩子元素沿着侧轴方向对齐的 align-items
,不同的值分别是:
-
flex-start
:将所有项目对着侧轴的起始方向对齐。 -
flex-end
:将所有项目对着侧轴的结尾方向对齐。 -
center
:将所有项目对着侧轴的中心对齐。 -
stretch
:将所有项目伸长,让项目占据了整个侧轴的长度。
这些值的名称都还蛮顾名思义的 ― 你可以玩一下上面的实例,改改 align-items
的值,或是直接看图 3。举例来说,实例最后用的代码是:
footer {
display: flex;
flex-flow: row wrap;
align-items: stretch;
}
然后就有点样子了 ― 不管随着窗口的高度与宽度怎么造成父容器的改变,那些页脚项目的高度维持是父容器的高度。这真是太酷了 ― 以前你想过 “要让一列的容器虽然有不同量的内容但是具有相同的高度” 想过了几次呢?是不是使用过不可适应内容的固定 height
?或是使用过背景图像制造的等高假象?
注:另外还有一个属性:align-self
可以让你为单独的伸缩子元素设置 align-items
的行为,会覆盖 align-items
。
沿着主轴排齐内容
这个种类里的另一个常用的属性是让项目沿着主轴排列的 justify-content
,也就是它指定子元素间多余的空白要如何处理。在你让子元素与其外边距占满主轴上的所有空间的时候(例:我的主打范例),这个属性没有作用。因此我做了另一个范例来演示 justify-content
― 请看看伸缩布局范例之固定宽度。
在这个例子里,我让各个伸缩子元素占有主轴宽度的一个比例:
#first {
width: 25%;
}
#second {
width: 40%;
}
#third {
width: 25%;
}
然后在父容器里我将 justify-content
的值设为:
footer {
display: flex;
flex-flow: row wrap;
align-items: stretch;
justify-content: space-around;
}
这个值效果很好 ― 它将空白均分到子元素之间与外侧,外侧的空白是元素之间空白的二分之一。其他可用的值为下列所示:
-
flex-start
:将伸缩子元素捆在主轴起始端,将空白留到结尾端。 -
flex-end
:将伸缩子元素捆在主轴结尾端,将空白留到起始端。 -
center
:将伸缩子元素捆在主轴的中间位置,将空白平均分到两端。 -
space-between
:跟space-around
的效果很相像,但是没有分配空间到开始、结尾两端。
图 4 显示了 justify-content
不同的值的效果:
多行伸缩容器的对齐:align-content
在使用 wrap
关键字的多行伸缩容器上,你也可以指定行与行之间空白的分配方法。align-content
仅在伸缩容器在侧轴占有固定的空间时(例:伸缩子元素横排的时候伸缩容器有固定高)才有作用。可用的值有:
-
flex-start
:将伸缩行捆在主轴起始端,将空白留到结尾端。 -
flex-end
:将伸缩行捆在主轴结尾端,将空白留到起始端。 -
center
:将伸缩行捆在主轴的中间位置,将空白平均分到两端。 -
space-around
:沿着侧轴将空白均分到伸缩行之间与外侧(行之间空白的二分之一)。 -
space-between
:跟space-around
的效果很相像,但是分配到开始、结尾两端的空间比较少。
改变元素的布局顺序
以前要不打乱源代码而改变元素的显示顺序是非常麻烦的,而伸缩布局就不同了。伸缩布局让你在子元素上设置 order
属性以改变元素在行或列上地出现顺序。这个属性接受一个整数值,值越大,元素排的越靠后。举例来说,回到我的伸缩胖页脚例子,重要链接默认是第二个子元素,如图 5 所示:
默认状态下,伸缩子元素的 order 都是 0。我们可以很轻易的给予不同的子元素不同的 order
来改变顺序。值越高,在伸缩子元素的列队中就出现的越晚,order
相同的子元素的顺序由源码的顺序决定。因此,在我的例子中,我将重要链接的 order
设成 1 来让重要链接出现在最后面(请看图 6 的结果):
#second {
order: 1;
}
注:你也可以使用负的 order
值。
让元素可伸缩
也许伸缩布局最强大的特性就是可以在主轴的方向让子元素的长度(如果 flex-flow
是 row
则为 width
,如果 flex-flow
是 column
则为 height
)依父元素的可用空间自由变化。这个特性通过 flex
属性实现,其值分为三部分,让我们逐个添加看其效果。首先,来看看正向伸缩值:
#first {
flex: 1;
}
#second {
flex: 1;
}
#third {
flex: 1;
}
这些没有单位的值作为比例使用 ― 它们决定了在父元素内的每个子元素应该占多少空间。如果每个都设置为 1,则每个子元素将在伸缩容器内被设置为相等的尺寸。如果你给其中一个子元素设置为 2,那么这个子元素将占用其他元素两倍的空间:
#first {
flex: 1;
}
#second {
flex: 2;
}
#third {
flex: 1;
}
你还可以给每个子元素都设置一个伸缩基准值,就像这样:
#first {
flex: 1 200px;
}
#second {
flex: 2 300px;
}
#third {
flex: 1 250px;
}
首先,每个子元素的长度是伸缩基准值。然后,父元素内剩余的空间将按照 flex
中设置的正向伸缩值分配到每个子元素,这才是它们的最终尺寸。所以子元素在主轴方向的尺寸先被设置为 200px、300px 和 250px,总共 750px。如果父容器在主轴的尺寸为 950px,那么浏览器将会剩余 200px 分配给这些子元素。因为 #first
和 #third
的正向伸缩值是 1,它们各将被分配 50px,最终的尺寸分别为 250px 和 300px。因为 #second
的正向伸缩值 为 2,它将被分配 100px,最终尺寸是 400px。
flex
的第三个值很少用到,但是以防万一我们还是来看一下。你可以给子元素设置一个负向伸缩值,就像这样:
#first {
flex: 1 1 400px;
}
#second {
flex: 2 3 600px;
}
#third {
flex: 1 2 400px;
}
虽然说是负向伸缩值,但是其实它们的值是正数 ― 上面宣告中第二个没有单位的值。这些值仅在子元素在主轴方向溢出父容器时用到。同样是比例值,但是这一次它们指定的是将被扣除的 “溢出量”(子元素溢出容器的部分)的比例(但是以伸缩基准值加权),使得整体尺寸减小到与父元素相等 ― 也就是,停止溢出。
假设父元素在主轴的方向尺寸为 1100px,在这种情况下,上例中的子元素就会溢出 300px(在主轴方向总尺寸为 1400px),因为设置了负向伸缩值,加权综合为 400px × 1 + 600px × 3 + 400px × 2 = 3000px:
-
#first
将被移除溢出量的 400px/3000px = 2/15,也就是 40px,因此计算后的尺寸为 360px。 -
#second
将被移除溢出量的 600px × 3/3000px = 9/15,也就是 180px,因此计算后的尺寸为 420px。 -
#thrid
将被移除溢出量的 400px × 2/3000px = 4/15,也就是 80px,因此计算后的尺寸为 320px。
所以负向伸缩值越大元素越小!
下面是我的例子的最终值:
#first {
flex: 1 0 7rem;
}
#second {
order: 1;
flex: 2 0 8rem;
}
#third {
flex: 1.5 0 7rem;
}
伸缩布局:自适应方面的优势
在我的例子中有一个效果非常好的组合技:使用多行伸缩项目(flex-flow: row wrap
)、带有伸缩基准值的子元素(例如 flex: 1 0 7rem;
)和媒体查询。在较窄的窗口宽度下,要不溢出父元素而让子元素达到伸缩基准值是不可能的。在这种情况下浏览器会将伸缩容器的子元素自动折为多行,让界面看起来很流畅,如图 7 所示。
如此的强大和灵活,如此少的代码,真是一个好东西!
优雅降级
伸缩布局是一个全新的布局模式,不支持的浏览器会直接忽略,这可能会阻止你使用它。其实,大可不必。你可以用浮动或者 CSS 表格来做一个网站的 “桌面版”,而在宽度非常窄的浏览器使用伸缩布局,比如用来把左侧的导航移到主区域下面。
如果你在使用伸缩布局之前先将所有页面元素(nav、header、footer 等)设置为 display:block;
那么一个支持媒体查询但是不支持伸缩布局的浏览器会直接把内容铺满整个设备宽度。它们将保持原有的顺序,但是所有内容对用户都是可见的。
另外,CSS 表格可以用来将一个元素从原有的位置移动到内容区的开始或者结尾,参见《filthy table-caption》。
总结
我希望这片文章已经清楚的说明了伸缩布局是多么的有用、强大,让我们能够避免滥用 float
和 clear
以及其他一些令人望而生畏的非常不灵活的布局技术。你也应该了解到伸缩布局在响应式 Web 设计中的表现有多优秀,特别是与媒体查询等其他技术结合使用的时候。