本系列第一篇讲到如何创建网格容器,以及在容器元素上能够使用的属性。网格格式化上下文一旦创建,你也就有了网格线了。有了网格线,你就能在网格项目上添加属性,对项目做定位(place items)了。
读完本篇文章,你将学到:
- 定位属性:
grid-column-start
、grid-column-end
、grid-row-start
、grid-row-end
以及对应的简写属性grid-column
和grid-row
。 - 如何使用行号(line number)设置
grid-area
属性。 - 如何根据命名网格线(line name)定位项目。
- 在定位项目时,显式网格和隐式网格上的表现有何不同。
- 使用 span 关键字,再讲一点福利内容 subgrid。
- 当混合使用自动布局(auto-placed)和确定布局(placed)定位项目时,需要注意些什么。
网格线定位
在网格中定位一个项目时,需要先设置它从哪根线开始,到哪儿根线结束。举个例子,我要在一个 5x5 的网格中定位一个项目,让它占据第二列和第三列,第一行到第三行。我会使用下面的 CSS 代码(注意,这里使用的是网格线,而非网格轨道)。
.item {
grid-column-start: 2;
grid-column-end: 4;
grid-row-start: 1;
grid-row-end: 4;
}
上面的代码还可以简写为以下形式:斜线之前表示起始线(start line),斜线之后表示终止线(end line)。
.item {
grid-column: 2 / 4;
grid-row: 1 / 4;
}
效果:
注意,虽然 .item
的内容很少,但还是占满了整个定位区域。这是因为项目上的对齐属性 align-self
和 justify-self
的默认值为 stretch
。
如果项目只需跨越一个轨道,那么可以忽略设置终止线,因为项目默认就跨域一个轨道。举个例子,设置一个只占据第二列的项目。之前会这么写:
.item {
grid-column: 2 / 3;
}
其实可以简写成这样的:
.item {
grid-column: 2;
}
grid-area
属性定位
我们还能用 grid-area
属性定位项目。下一篇文章会全面讲它的用法,不过现在我们只讲使用行号来设置它的方式:
.item {
grid-area: 1 / 2 / 4 / 4;
}
grid-area
属性行号的设置顺序是这样的:grid-row-start
、grid-column-start
、grid-row-end
、grid-column-end
。如果你是在水平、从左到右的语言环境下开发的(比如英语),那么这几个分别对应的方向是 top
、left
、bottom
和 right
——跟设置 margin
的方向是相反的——逆时针。
这种设置网格线的方式是为了能适配不同的书写模式(下面会介绍)——先设置起始端,在设置结束端,这与代表物理方向的 top
、left
... 是不同的。当然,上面这种设置项目位置的方式我不会用,还是会使用 grid-column
和 grid-row
这两个简写属性,因为它们的可读性更高。
显式网格中的网格线
在一个网格容器中,使用 grid-template-columns
或 grid-template-rows
设置的那部分网格区域称为 显式网格。在定义显式区域的同时,还会定义网格线。
这些网格线会编号,起始值是 1
,在行内和块方向两个维度上编号。对水平书写模式、从左向右排版的语言来说:行内方向(inline direction)的编号从左开始;块方向(block direction)的编号则从上面开始。
这里的“行内方向”和“块方向”的概念需要介绍一下:
我们平时在开发网站时,所浏览中、英文网页文本排版方式,基本都是从左到右、从上到下的。我们把> 文字书写方向就叫做行内方向(inline direction),文字折行方向叫做块方向(block direction)。
同样的,如果是垂直书写语言——像中国古籍里排版方式——那么从上到下就是指行内方向,从右到左是块方向。
如果是在水平 RTL(right to left)语言环境下,比如阿拉伯语。此时,块方向仍然从上面开始编号,但行内方向编号就是从右开始的了。
当然,如果是在垂直书写模式下,比如下图里的网格就设置了 writing-mode: vertical-rl
。那么块方向则是从右面开始编号的,行内方向则从上面开始编号。
因此,网格线的编号是跟书写模式、文档或组件的语言环境存在一定关系。
另外,显式网格的最后一根网格线可以使用数值 -1
指代,同理倒数第二第三跟网格线分别是 -2
、-3
,依次类推。假设·,现在有一个项目从网格的第一列横跨到最后一列,那么可以这样写:
.item {
grid-column: 1 / -1;
}
隐式网格中的网格线
隐形网格的网格线也是从 1
开始编号的。假设我创建一个网格,只显式指定了列(使用 grid-template-columns 属性),并未显式指定行,只使用 grid-auto-rows: 5em
指定了隐式行的尺寸。
在下面的网格布局中,我为一个项目添加了 .placed
类,指定它从第一行跨越到最后一行。
<div class="grid">
<div>1</div>
<div>2</div>
<div>3</div>
<div>4</div>
<div>5</div>
<div>6</div>
<div class="placed">Placed</div>
</div>
<style>
.grid {
display: grid;
grid-template-columns: repeat(5, 100px);
grid-auto-rows: 5em;
}
.grid > .placed {
background-color: orange;
grid-row: 1 / -1; /* the end line is in the implicit grid so -1 does not resolve to it*/
}
</style>
我们来看看结果:
.placed
的定位结果与我们想象的有出入,按道理应该占两行才对,实际只占了一行。这是因为现在的行轨道没有使用 grid-template-rows
显式创建,导致行号 -1
被解析为 2
,而非 3
。
现在还没有办法定位到隐式网格中的最后一根网格线,因为并不知道一共有多根网格线。
使用命名网格线定位项目
除了使用行号定位项目,我们还你能使用命名网格线来定位项目。一条网格线可以取多个名字,名字包围在方括号 []
中,网格线名称在各轨道尺寸(tracks sizes)之间定义的。
.grid {
display: grid;
grid-template-columns: [full-start] 1fr [main-start] 2fr 2fr [main-end full-end];
}
有了命名网格线,就可以用它来替换默认行号来定位项目。
.item {
grid-column: main-start / main-end;
}
效果:
如果一根网格线定义了多个名字,那么你可以任选其一使用。这么多名称最终都是指代同一根网格线。
如何处理多个网格线重名的情况?
这是一个很有趣的场景,多个网格线使用了同一个名字。这会发生在使用了 repeat()
函数的地方。下面例子中,我定义了一个八列网格,是通过 repeat(4, [sm] 1fr [lg] 2fr)
重复四次得到结果。还将较小轨道尺寸左边的网格线命名为 sm
,较大轨道尺寸左边的网格线则命名为 lg
。
这时,如果要指定是具体哪根网格线的时候,就要用到索引了。比如,我想把一个项目从第二根 sm
网格线延伸到第三根 lg
网格线,我会使用 grid-column: sm 2 / lg 3
。另外,如果没给网格线指定索引的情况下,默认将解析到第一根叫这个名字的网格线。
<div class="grid">
<div class="item">Item</div>
</div>
<style>
.grid {
display: grid;
grid-template-columns: repeat(4, [sm] 1fr [lg] 2fr);
grid-template-rows: repeat(5, 50px);
}
.item {
grid-column: sm 2 / lg 3;
grid-row: 1 / 4;
}
</style>
效果:
使用关键字 span
有些情况,只需要一个项目跨越一定数量的轨道,但不知道确切的网格位置。比如,当使用自动定位算法(auto-placement)定位项目的时候,只知道它们跨越了多个轨道,而非默认的就跨越一个。这个时候,就要使用关键字 span
了。
下面举了一个例子, .item1
设置了 grid-column: auto / span 3
:
<div class="grid">
<div class="item1">Item 1</div>
<div class="item2">Item 2</div>
</div>
<style>
.grid {
display: grid;
grid-template-columns: repeat(6, 1fr);
grid-template-rows: repeat(5, 50px);
}
.item1 {
grid-column: auto / span 3;
}
.item2 {
grid-column: auto / span 2;
grid-row: auto / span 2;
}
</style>
来看看效果:
将来 grid-template-columns
和 grid-template-rows
属性开始全面支持 subgrid
值之后,这种技术将变得非常有用。比如,在卡片布局中,每个卡片包含一个标题和内容区域,我们希望这些卡片里的内容也是彼此对齐的,那么可以通过为卡片设置 grid-template-rows
设置为 subgrid
来控制卡片从父网格中继承(两)行,这样就能实现卡片内容的自动对齐效果了。
<div class="grid">
<article class="card">
<h2>This is the heading</h2>
<p>This is the body of the card.</p>
</article>
<article class="card">
<h2>This is the heading and some headings are bigger</h2>
<p>This is the body of the card.</p>
</article>
<!-- ... -->
</div>
<style>
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
}
/*
* 1. `.card` 既是一个网格项目(隶属于 `.grid`),也是一个网格容器。
* 2. `.card` 作为网格项目占据两行轨道,它在行上的行为表现会传递给作为容器的它的子项目。
* /
.card {
grid-row: auto / span 2; /* 2 */
display: grid; /* 1 */
grid-template-rows: subgrid; /* 2 */
}
</style>
看下效果。在 Firfox 中查看,Chrome 中目前不支持 subgrid
值):
基于网格线定位层叠项目
网格系统会自动将项目定位到网格上的空单元格中,而不会遇到项目被定位到同一个单元格中的情况。但是可以基于网格行号、将不同的项目放入同一网格单元中。下例中,我设置了一个跨越两行轨道的图片,还有一个位于第二行轨道的有半透明背景效果的标题文本。
<div class="grid">
<figure>
<img src="https://images.unsplash.com/photo-1576451930877-c838b861e9b6?ixlib=rb-1.2.1&q=85&fm=jpg&crop=entropy&cs=srgb&ixid=eyJhcHBfaWQiOjE0NTg5fQ" alt="lights">
<figcaption>This is the caption</figcaption>
</figure>
<!-- ... -->
</div>
<style>
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
}
figure {
display: grid;
grid-template-rows: 300px min-content;
}
figure img {
object-fit: cover;
width: 100%;
height: 100%;
grid-row: 1 / 3;
grid-column: 1;
}
figcaption {
grid-row: 2;
grid-column: 1;
background-color: rgba(0,0,0,.5);
color: #fff;
padding: 10px;
}
</style>
效果:
这些项目将按照它们在 HTML 中出现的顺序排列。上例中,标题处于图片之后,因此会显示在图片上面。如果标题在前面,那么它就会挡在图片后面,我们就看不见了。另外,如果标题必须在图片前面,那么你可以通过使用 z-index
属性来控制层叠顺序,让图片显示。
混合使用网格线定位与自动定位
如果你混合使用网格线定位与自动定位,需要多加小心。当项目是根据自动定位算法定位的时候,会依次将自己定位到网格上、下一个可用的空白空间。
<div class="grid">
<figure>...</figure>
<figure>...</figure>
<figure>...</figure>
<!-- ... -->
</div>
<style>
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
}
figure {
margin: 0;
display: grid;
grid-template-rows: 200px min-content;
}
figure:nth-child(odd) {
grid-column: auto / span 2;
}
figure:nth-child(2) {
grid-column: auto / span 3;
}
</style>
效果:
网格默认的定位行为是这样的:如果当前剩余的空白空间不足以放得下项目,那么就换行显示,这样就会导致上一行会留下空白。你可以通过设置 grid-auto-flow
值为 dense
(该单词为“密集”的意思,意思即尽可能密集的排列项目)来控制这种行为。在这种情况下,如果当前剩下的空白区域足以放得下后续的某个项目,那么这个后续项目会自动挤上来显示,这会导致显示顺序与源码顺序不一致。我们对上例稍作修改,添加一个grid-auto-flow: dense
设置。结果发现项目 3 放在项目 2 之前显示了。
.grid {
/* ... */
grid-auto-flow: dense; /* 增加了这一句 */
}
效果:
请注意,此行为可能会导致用户使用 Tab 键浏览文档时出现问题,因为视顺序与源码顺序不同。自动定位算法会寻找第一个可用的间隙来排布网格项目。打个比方:布局时把头几个网格项目避开顶部区域,留下空白,那么自动定位算法会将后续合适尺寸的项目安排到这些轨道中。
我这里再举一个例子(也是本篇最后一个例子):我们基于行号定位(第 1 项和第 2 项)将第一行的空间空了下来,随后会看到后来的项目会向上移动来填补这些空白空间。
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
}
figure:nth-child(odd) {
grid-column: auto / span 2;
}
figure:nth-child(1) {
grid-row: 2;
grid-column: 1;
}
figure:nth-child(2) {
grid-row: 2;
grid-column: 2 / -1;
}
效果:
看到没?第一、第二个项目被定位到第二行显示了,空下来了第一行,这时后续的第三、第四个项目被自动定位算法推到了第一行。
注意,在动图的最后我还做了一件事情,就是在网格容器上设置了 grid-auto-flow: dense
,导致第六个项目位置上移了。这是为了让大家区分自动定位与密集定位:
- 自动定位在占据空白的同时,会遵守项目顺序——排到空间区域的项目顺序与源码中出现的顺序是一致的。
- 而密集定位则不管顺序——只要空白空间放得下我,我就要过去。
自动定位算法非常值得我们理解的。这对于理解某些场景下的布局表现会有帮助——比如,如果在网格中新增加了一个项目但没有设置定位区域,则它可能不会像我们所想的那样排在网格的最后面,而是出现在比较靠前的某个“奇怪”位置。
总结
关于网格线的内容还是挺多的。需要记住的是,网格行号从网格创建出来的那一刻起就随之产生,你可以通过指定初始行号和结束行号来定位一个项目。在下一篇文章,我还会介绍另一个定位网格项目的形式:grid-template-areas
。
(正文完)
从最零基础开始的的HTML+CSS+JavaScript。jQuery,Ajax,node,angular框架等到移动端HTML5的项目实战【视频+工具+系统路线图】都有整理,在线解析,学习指导,点:【WEB前端学习圈⑤】