11.9 CSS 网格布局概述

网格布局是为了适应网页交互技术的发展

Grid layout可以用来构建各种布局,擅长把网页应用的空间划分为多个区域,或者定义各个HTML部件之间的尺寸、位置和层次关系。

在没有grid layout之前,作者常会使用table来构建多列布局。grid layout和table一样可以用来把元素按照行列对齐。

不过我们前面在html文档结构相关章节提到过,使用table来给整个网页应用布局在语义层面上是不正确的。

在功能上,和table不同的是,grid layout没有固定的内容结构(content structure),所以在布局上有更大的灵活性。比如,grid容器的子元素可以自行定位,和定位元素一样来实现覆盖和分层。
而且,没有内容结构使得grid layout可以通过使用流式(fluid)和文档序无关的布局技术来响应布局的变化。 通过和媒体查询(media query)绑定的CSS属性来控制grid容器及其子元素,作者可以按照目标设备外形因素、屏幕方向和可用空间来动态调整布局,而无需改变内容的语义特性(semantic nature)。

为什么需要引入网格布局

Image: Application layout example requiring horizontal and vertical alignment.
需要行列对齐的Web应用布局示例

随着互联网的发展,网站已经从单纯的文档演变成了多样化复杂的交互应用系统, 一些以往被作者使用的文档布局工具,例如浮动(floats)、表格(tables)结合一些JavaScript脚本已经显得过时而低效。 在适应可用空间有限且尺寸变化跨度较大的移动设备上容易出错,常会导致一些违反直观感觉的意外情况(我们前面提到过浮动属性的复杂性)。 这使得作者宁可选择一个固定布局来获得稳定可靠的行为,而牺牲掉设备可用空间的自适应性。

引入grid layout布局正是为了适应Web应用的发展,解决上述问题。它提供一个新的机制,允许作者使用一些可靠可预测的度量方法来把空间划分为行列布局。 然后作者就可以精准的把构建块元素放置到由这些行列所定义的网格区域(grid areas)中去。 图1展示了一个使用grid layout所能创建的基本布局。

布局自适应可用空间

Image: Five grid items arranged according to content size and available space.
在小屏幕上的5块区域布局
Image: Growth in the grid due to an increase in available space.
屏幕空间变大后各个区域的扩展

Grid layout 可以用来智能的调整(reflow)网页中的元素。 图2展示了一个页面被划分为5个主要区域的游戏应用:标题、状态栏(stats area)、游戏面板(game board)、记分牌(score area)和控制栏(control area)。 作者的设计意图有如下几点:

  • 状态栏总是紧贴在游戏标题下方。
  • 游戏面板位于标题和状态栏的右侧。
  • 游戏标题的游戏面板的顶端沿横向对齐。
  • 当游戏页面处于最小高度时,游戏面板和状态栏的底端沿横向对齐。否则,游戏面板将尽可能扩展自己来占据实际可用屏幕空间。
  • 记分牌应该和状态栏、游戏标题列向对齐。而控制栏在游戏面板的下方居中。

相比于使用脚本编程来控制所有元素的绝对位置和尺寸,我们也可以使用grid layout,如图3所示。 下面的例子演示如何使用声明(declaratively),而不是编程的方式(programmatically),来获得所有这些大小,位置和对齐规则。

有多种方法来指定grid的结构、网格项(grid items)的位置和尺寸,各自适用于不同的场合。 本例的方法是,在网格容器(grid container)上使用网格模板行(grid-template-rows)网格模板列(grid-template-columns)属性, 以及在每个grid item上使用网格行(grid-row)网格列(grid-column)属性。

#grid {
  display: grid;

  /* 定义两列:第1列大小自动适配其内容,第2列占据其他可用空间,
   * 但是不小于Board或Controls的最小宽度。 */
  grid-template-columns: auto minmax(min-content, 1fr);

  /* 定义三行:第1和第3行大小自动适配其内容,第2行占据其他可用空间,
   * 但是不小于Board或Stats区域的最小高度。 */
  grid-template-rows: auto minmax(min-content, 1fr) auto
}

/* 游戏的每个部分摆放网格线(grid lines)之间,通过引用起始网格线,
 * 然后指定(如果不止1个的话)行或列跨度(span)的数字来确定终止线,
 * 这样就建立一个区块边界。 */
#title    { grid-column: 1; grid-row: 1 }
#score    { grid-column: 1; grid-row: 3 }
#stats    { grid-column: 1; grid-row: 2; align-self: start }
#board    { grid-column: 2; grid-row: 1 / span 2; }
#controls { grid-column: 2; grid-row: 3; justify-self: center }
    
<div id="grid">
  <div id="title">Game Title</div>
  <div id="score">Score</div>
  <div id="stats">Stats</div>
  <div id="board">Board</div>
  <div id="controls">Controls</div>
</div>

布局的解释已经写在上面的代码和图2中。网格布局语法之强大,由此例可窥一斑。

代码序无关性(Source-Order Independence)

Image: An arrangement suitable for portrait orientation.
适配竖屏方向(portrait orientation)
Image: An arrangement suitable for landscape orientation.
适配横屏方向(landscape orientation)

接着前面的例子,我们希望这个游戏能自适应桌面电脑显示屏、手持设备或平板电脑的可用空间。 同时,这个游戏的界面组件应该能自动根据屏幕的方向来优化其摆放位置。(图 4 和 5)。 通过结合使用grid layout和媒体查询(media query)技术(我们在CSS进阶课程中的响应式技术章节会详细讲解该特性), 作者能够使用相同的语义标记,但独立于其代码顺序来重新安排元素的布局,以在两个屏幕方向上获得不同的外观。

下面的例子中,我们给放置grid item的空间设置名字,这样当网格的定义改变时,可以避免为grid item重写规则。

网格布局重新排列(reordering)功能的设计意图仅是为了影响视觉渲染,而不会影响基于代码顺序的语序(speech order)和浏览。

这给予作者操控网页视觉表现的能力,但同时仍能保持文档内在顺序不变,这种内在顺序通常适合于非CSS用户代理和线性模型(比如语音阅读和序列化浏览)来访问。 因此网格项(Grid item)的排列不能被用来替代正确的代码书写顺序,因为这可能会破坏文档的可访问性(accessibility),比如对于有视觉障碍的人群。

元素的网格层次(Grid Layering)

Image: A control composed of layered HTML elements.
由层叠的HTML元素构建的界面组件

在图6所示的例子中,我们想创建一个自定义的滑块控制器(slider),包含6个部分。 整个控件有六个部分。下标(lower-label)和上标(upper-label)和左边缘的控制都左对齐。滑块的轨道(track)横跨了两个标签之间的区域。下填充和上填充部分在滑块(thumb)底下接触。滑块拥有固定的宽高,通过更新弹性尺寸(flex-sized)的左右两列的宽度来沿着轨道移动。

在引入网格布局之前,作者有可能会使用绝对定位来控制该控件中每个元素的顶部和左边坐标,以及宽高。而利用网格布局,作者可以使用简单的脚本,根据网格容器(grid container)网格模板列(grid-template-columns)属性的变化来定位到不同的轨道位置。