原文出处: w3cplus - 南北
在众多浏览器刚刚支持 css 的时候,我就已经开始使用它了,并且应该算是最早采用 CSS 进行页面布局的开发者之一了。那时候,浏览器之间的兼容性虽然不好,但我仍然热衷于尝试层出不穷的新特性。最近几年在 CSS 领域,我们看到了许多重大进展。其中,web 字体、渐变、阴影和媒体查询已经成为了我们日常工作流的必备工具。
然而,CSS 布局却发展缓慢。开发者们曾经尝试使用display:table和display:inline-block来布局,用来缓解绝对定位和浮动布局所带来的束缚。然而,这些方法并不标准,因此它们又产生了新的问题。
CSS 布局的未来看起来一片大好。在这篇文章中,我将会介绍 CSS 规范中一些激动人心的布局模块。在未来,我们可以更有效地实现网格布局,更轻松地创建等高列或者均匀分配内容到整个页面。类似 Adobe 的公司往往熟悉布局设计的细节,借助它们的帮助来制定相关规范,我们就能更准确地控制页面在浏览器上的显示方式,同时避免对页面内容的影响。
对于本文中示例,我已经在一个或多个浏览器上进行了测试,当然你也可以继续测试它们。这些布局模块中的一部分可能还处于发展的初期阶段,其具体实现未来可能还会有变化,所以,你也可以就相关问题反馈给制定标准的团队。这是我们的 web,我们应该热衷于参与到制定规范的工作中。
如果你在线上产品中使用本书中的这些技巧,那么你要确保页面对低版本浏览器用户同样是友好的??即使这些低版本浏览器并不支持相关的布局模块。虽然我不想花过多的时间解决浏览器的兼容性问题,但在每一章节的最后,我会给你一些有用的建议和提示。
浏览器前缀
本文所涉及到的大部分属性,都会需要多个浏览器前缀。对于那些规范比较稳定的布局模块,比如多列布局和 flexbox 布局,我会使用 Lea Verou 的 -prefix-free 脚本,从而只需要演示标准属性,也可以实现跨浏览器的兼容性。对于线上产品,我建议你要么为 CSS 添加浏览器前缀,要么使用 CSS 预处理器将浏览器前缀编译到最终的 CSS 文件中。
对于那些非常新的布局模块(只被个别浏览器所支持,相关规范频繁变动的布局模块),我会为其添加所测试浏览器的前缀。在本书出版之时,通过添加特定浏览器前缀,其他浏览器可能也已经支持相关布局模块了。鉴于这些布局模块的实验性特征,所以在不同浏览器上可能会有不同的渲染结果。应该尽可能地为其他浏览器使用前缀并做相关的测试,最后在线上产品中还要加上无前缀的相关属性。
多列布局
CSS3 的多列布局已经风靡多年,然而,由于 IE 的不支持,它并没有获得预期的受欢迎程度。在 IE10 支持这些特性之后,它对于响应式设计就显得更为有用了??非常期待它能流行起来。多列布局模块是本书提到的所有模块中,技术最成熟、浏览器支持度最高的一个模块,所以从它开始讲述 CSS 中的新布局模块是一个不错的选择。
多列布局使内容均匀分散到多列成为可能,它非常类似于报纸中的“内容流动”效果。首先,你需要在文档中选择一个容器(container),然后声明该容器具有多列布局,那么浏览器就能实现预期的多列效果。如果你为内容(content)指定了列数,浏览器就会自动计算出每一列的宽度,自动适应父级元素的尺寸;如果你指定了每一列的宽度,那么浏览器就会自动计算列数,当父级元素尺寸发生变化,浏览器还可以自动重新计算。
示例:设置column-width-查看示例
.col-wrapper { column-width: 220px;}
设置column-width属性意味着要求浏览器尽可能多地为容器创建纵列(columns),而开发者指定的宽度则会被视为理想宽度。你可能已经注意到了,当我们指定单列的宽度时,实际上并没有得到预期的宽度。多列之间往往填充了空白(space),这有助于浏览器根据指定的宽度计算最合适的列数。CSS 多列规范中是这样解释的:
“column-width的值指定了最理想的单列宽度。实际的单列宽度可能会更宽一些(多列之间填充了空白),或更窄一些(只有可用宽度小于指定宽度时才会出现这种情况)。该属性的指定值必须大于0。”
因此,当需要设定单列宽度时,你只需指定一个理想宽度即可,因为为了设计的灵活性,实际宽度可能会出现一些差异。
示例:设置column-count-查看示例
你也可以使用column-count指定所需要的列数,然后让浏览器自行决定单列宽度。
.col-wrapper { column-count:3; }
调节间距
在上面的例子中,你会发现多列之间并没有紧紧相邻:这是因为多列之间存在间距(gap)。在多列布局中,间距是由column-gap属性控制的。如果将column-gap设为0,那么多列之间就不会有间距,所有的文字会拥挤在一起。规范中建议浏览器在默认样式中为该属性预设1em的宽度。不过,如果让多列的间距保持一致性,那么你应该显式地为该属性设定一个值。
示例:设置column-gap属性-查看示例
.col-wrapper { column-width: 220px; column-gap: 1.5em; }
美化多列布局
在最新的规范中已经限制了对多列布局的美化。不过,相关的工作草案也提示说:“或许在未来的规范中会添加额外的功能。比如,允许多列中的任意一列设置不同的宽度和背景。”目前来说,你还不能单独地美化多列中的某一列。
虽然没法设置单列的内外边距、宽度和背景色,但是我们可以使用一些规则来隔离多列。实现这一布局效果需要使用column-rule属性:
- column-rule-style
- column-rule-width
- column-rule-color
这些属性的用法非常类似border-style、border-width和border-color,而且也可以使用column-rule属性作为一种简写形式:
column-rule: [width] [style] [color];
上面的纵列属性(column rules)将会被应用到column-gap上。要想修改纵列两边的空白间距,就需要调整column-gap的属性值。如果上述纵列属性的值大于可用间距,那么它就会与文本区重叠??但它不会占用任何的空间。
示例:使用column-rule-查看示例
单列跨度(span)
如果你想让某一个元素延伸到所有的纵列中,那么可以为该元素添加column-span并赋值为all。在下面的示例中,我要让h1标题可以延伸到所有的纵列上。
示例:强制h1延伸到多列-查看示例
.col-wrapper h1 { column-span: all; padding: 0 0 .5em 0; }
当前的规范中column-span只有两个值:all和none。
多列截断
当使用多列布局的时候,你需要控制多列截断的方式。如果不希望某些元素被截断到新的纵列,或者确保某个元素固定在某一列时,下面的这个属性对你就会很有帮助。
示例:避免在段落内和引用块之前被截断-查看示例
.col-wrapper p { break-inside: avoid; } .col-wrapper blockquote { break-before: avoid; }
印刷和分页媒体类型
规范指出,多列布局内部的元素不应该出现在下一页上。如果阅读时翻到了下一页,然后让用户再返回前一页继续阅读,那么这样的体验就太让人恼火了。
你可以预设分页时段落或者元素内部内容的布局方式??就像控制多列截断一样。属性avoid-page和avoid-column就可以帮助你实现良好的控制。如果你允许段落进行截断而不允许内容分页,那么对于上面的例子,就可以使用break-inside: avoid-page替代break-inside: avoid;。
响应式设计
由于多列默认就是响应式的,所以多列布局也有助于实现响应式设计。正如我们所知,虽然可以根据需求设定单列宽度,但本质上浏览器会使用自己的算法,计算出一个用于渲染的宽度。
单列内的图片会被限制在单列范围之内,所以使用max-width设置的最大宽度也被局限在单列之内。如果你没有为图片设置max-width: 100%;,同时图片的宽度还大于单列宽度,那么浏览器就会自动裁剪掉多余的部分。该规则同样适用于其他宽度大于单列宽度的元素。
示例:图片宽度限制于单列之内-查看示例
一定不要认为多列布局模块只适用于创建类似报纸的版式。下面示例中的多列布局,就包含了一系列的盒模型和图片。
示例:盒模型和图片-查看示例
下面的示例演示浏览器视口较窄时的单列布局,无需编写额外的代码即可实现。
浏览器提示
当我写下这些文字的时候,还只有几个浏览器支持多列布局。因此,有时你需要添加浏览器前缀才能使用这些属性,此外,在有一些情况下某些属性并不会生效。更多更新的浏览器支持信息,可以查看 Can I Use。如果浏览器不支持多列布局,那么它在解析样式表时就会忽略相关属性,因此可以放心使用这些属性。不支持多列布局的浏览器会将内容渲染为单列,这种渲染结果在大多数情况下也是可以接受的??不建议使用腻子脚本模拟多列布局效果。
CSS Flexbox布局
CSS 弹性盒布局模块,通常被称为 flexbox,为我们提供了一种新的布局??flex 布局。设计该布局的初衷是为了简化复杂应用和页面的布局代码。在本节中,我将会着重介绍一些使用 flexbox 解决的布局问题。
均匀排列
在传统的布局设计中,将一组布局元素沿坐标轴均匀排列是件很麻烦的工作。如果使用浮动布局,那么每个浮动元素都必须设置一个宽度,否则就会宽窄不一,此外,往往需要使用 JavaScript,才能让所有的浮动元素均匀排列在同一行上。
Flexbox 极大简化了这一布局过程。在下面的示例中,我使用无序列表创建了一个导航条。
示例:flexbox 简单用法-查看示例
对于这个导航条,我想实现水平均匀分布的效果。如果我们选择 flexbox 布局方式,那么只需要添加display:flex属性,并指定布局元素的排列方式(所有元素平均排列或者除首尾元素外平均排列)。
示例:flexbox 简单用法-查看示例
nav ul{ margin: 0;?padding: 0;?list-style: none;?display: flex;?justify-content: space-between;}
在这里,我们将justify-content设置成了space-around,这样做的好处就是让每一个元素之间具有相同的间距,避免了溢出容器的问题。此外,在第一个元素之前和最后一个元素之后,也添加了相同的间距。
上面的示例使用了一些 flexbox 的默认属性,比如这里的布局元素默认显示为水平排列。这个默认的排列效果等同于添加了flex-direction: row的效果。
flex-direction属性一共拥有四个属性值:row、row-reverse、column和column-reverse。使用这些属性,可以实现水平排列、反向水平排列、垂直排列和反向垂直排列四种布局效果。
nav ul { margin: 0; padding: 0; list-style: none; display: flex; justify-content: space-between; flex-direction: row-reverse;}
使用 flexbox 的另一个优势在于,它可以帮你创建等高容器??即使容器内的文字不等长。align-items的默认属性值为stretch,它会根据 flexbox 内最高元素的高度,拉伸其他布局元素,使之等高。在我的导航示例中,如果你缩窄窗口,文字就会自适应为多行,但仍然保持等高拉伸的布局??从所有元素的边框就可以看出他们是等高的。
align-items的所有属性值:
- flex-start
- flex-end
- center
- baseline
- stretch
为了理解其正确的解析方式,你需要首先理解 flexbox 中的两个轴概念:主轴和侧轴,其中主轴用来控制元素的布局方向。通过将flex-direction设置为row或者column,可以指定主轴,并确定布局元素的排列方向:从左到右或者从上到下;第二条轴,即侧轴,垂直于主轴。
如果将flex-direction设为row,那么主轴方向就是从左到右的。设置align-items为flex-end,意味着布局元素不会占据布局容器的全部高度,所有的布局元素在底部对齐,不做等高处理。
多行 flexbox
在之前的这个示例中,如果我们将浏览器缩窄,那么布局元素内的文字就会在布局元素内拆分成多行。究其原因,是因为布局元素的内容宽度超过了可用空间。一种可行的解决方案就是将布局容器设为wrap(表现为多行效果)。
在 flexbox 布局中,使用flex-wrap属性可以控制布局容器的多行模式,可用的属性值包括wrap、nowrap和wrap-reverse。如果没有显式声明该属性值,那么flex-box默认为nowrap。
示例:布局容器的多行模式-查看示例
nav ul{ margin: 0; padding: 0; list-style: none; display: flex; justify-content: space-between; align-items: stretch; flex-direction: row-reverse; flex-wrap: wrap;}
然而,从图中可以发现,相邻布局元素之间出现了意料之外的不规则空隙。修正这种表现的方式是为布局元素添加额外的布局间距。为布局元素设置flex: auto;即可实现这一目标??在我们的示例中,我为li元素添加了该属性。
nav li { border: 1px solid #999; border-radius: 2px; flex:auto; margin: 0 1em 1em 0; text-align: center;}
使用flex属性
虽然为布局元素设置flex: auto;可以调整它们在布局容器内的空间排列,但该属性的作用远不止如此,这里将会介绍几种其他的用法。
在下面的示例中,我编写了三个块级元素,块级元素内部是一些关于长毛猫品种的信息。此外,我还为块级元素添加了公有的类名box和独立的类名,便于选择特定的块级元素。
示例:三个块级元素-查看示例
The Angora
...
The Persian Cat
...
The Russian Long-haired Cat
...
为了让所有的块级元素在单行内排列,我将布局容器,即boxes,设为了 flex 元素,然后为容器内的每个元素设置flex: 1。设置完成后,所有的布局元素就都具有了相同的宽度。
.boxes { display: flex;? flex-direction: row;? flex-wrap: wrap;? align-items: stretch; justify-content: space-between; } .box { border: 1px solid #999; border-radius: 5px; flex: 1;? margin: 0 1em 1em 1em; padding: 10px; }
此外,我们可能需要让某个布局元素宽于其他布局元素,同时还要根据可用的容器空间计算各个布局元素的宽度。示例中的第三个块级元素拥有一个类名box3,如果为box3添加flex属性并赋值为2,那么相比于flex: 1;的布局元素,它就会具有两倍的宽度。
.box3 { flex: 2;}
为 flex 布局元素排序
从前面的介绍中,我们已经了解到 flex 布局元素是可以反向排列的。实际上,我们可以为每个独立的布局元素设置任意的排列顺序,其中的关键,就需要用到下面介绍的order属性。
通过为每个布局元素设置order属性,我可以轻松移动box3。只需要为其添加order: 2;,就可以将这个最宽的布局元素移动到容器中央。
.box1 { order: 1;}.box2 { order: 3;}.box3 { flex: 2; order: 2; }
虽然我们更改了渲染后的布局顺序,但实际上它的 HTML 结构仍旧保持不变。这意味着你可以根据实际需求来编写 HTML 结构,这样做有助于提高可用性,改善使用文本阅读器的用户体验。此外,使用该属性还可以创建出色的布局效果。
响应式设计
对于响应式设计,flexbox 是一种优秀的可选方案。由于它具有包裹多行、自适应可用空间的特点,可以让我们不费吹灰之力就创建出简单的响应式效果。
如果你在 flexbox 中混合媒体查询的功能,那么就可以创建出更复杂的布局效果。由于可以按照不同于源码的结构来显示布局元素,所以我们能够在不同尺寸的屏幕上创建不同的布局效果。此外,也可以像下面的示例一样,使用flex-direction来转换布局方向。最初,我们可以让导航显示为垂直排列,当窗口宽度大于700px时,我们就可以将其转换为水平排列。
nav ul{ margin: 0; padding: 0; list-style: none; display: flex; flex-direction: column; justify-content: space-between; align-items: stretch; }@media only screen and (min-width: 700px) { nav ul { flex-direction: row; }}
浏览器提示
Flexbox 是一个极佳的案例,展示了 CSS 规范在初期的演变方式。由于 flexbox 的具体实现和最初构想之间已经发生了诸多变动,所以在 flexbox 的可用性上存在大量无效的资料。当你需要检索有关 flexbox 的资料时,可以查看一下 CSS Tricks 上的相关文章,便于检验当前资料的准确性。
当浏览器不支持 flexbox 时,一方面可以使用 JavaScript 来模拟 flexbox 的大量特性,另一方面你也可以让浏览器优雅降级,让布局元素呈现一种线性排列,下图就是之前的布局元素在浏览器不支持 flexbox 时的渲染效果。
如果你只是将 flexbox 布局模块应用于少量的界面元素,而不是整体布局,那么优雅降级为线性排列是比较简单的。另一种方式是使用 Modernizr 检测浏览器是否支持 flexbox,然后分别为支持和不支持 flexbox 的情况编写不同的 CSS 代码。
CSS 网格布局
CSS 网格布局模块是由微软提议的,目前仍在进行大量的规范化工作。该模块的最新进展已经应用到了 Internet Explorer 10 上,所以本部分的实例演示都会基于 IE10。
网格布局旨在解决复杂网页的布局问题??在该布局提出之前,我们只能通过元素的浮动和定位来模拟复杂布局。网格布局也允许开发者显示与源码结构不同的布局结构??类似上一章节的 flexbox 布局。我很喜欢网格布局,希望你在阅读完下面的示例之后也会喜欢它。
创建网格
将布局元素包裹进网格的第一步,就是在它们的父元素上创建网格。首先需要添加display: grid;(通常我会加上-ms-前缀),然后设置所需的网格行数和列数。
示例:一个简单的网格布局-查看示例
.wrapper { display: -ms-grid; -ms-grid-columns: 200px 20px auto 20px 250px; -ms-grid-rows: auto 1fr;}
上面的 CSS 代码在.class元素上创建了网格,该网格拥有五个纵列:一个200px宽度的侧列,一个20px宽度的间距,一个自适应宽度的中间纵列,另一个20px宽度的间距,以及最后一个250px宽度的纵列。由此可见,这是由两个固定宽度的纵列和一个可变宽度的中间纵列构成的简单布局。
在横列上,我将第一列设为了auto??它将根据内部内容自动扩展高度,将第二列设为了1fr。1fr准确的说是一个分数比值,在这里意味着第二列在剩余空间所占的比重。由此可见,这是具有两个横列的布局。
现在,我们可以往网格里添加一些内容了。添加内容后的结构如下:
Usefulness of cats
...