css grid 布局可以将元素变为一个网格,并且将它的 直接子元素 按照网格布局排列。扁平化的标签写起来更舒服,但会降低语义化的程度,这通常意味着对使用无障碍阅读的用户不友好。通过将 display 设置为 contents,我们可以将非直接子元素放在网格上,这样我们的布局扁平化和语义化兼具。下面我们来看看具体的做法吧。

在接下来的文章中,我将解释我所说的直接子元素和非直接子元素,以及展示我们是如何使用 display: contents 提升语义化程度的。注意:现在的浏览器对它的支持还不够完善,下面会详细提到。

网格只对直接子元素起作用

在网格布局中,只有直接子元素才会成为按照格子进行排列布局。为了照顾对这个语法不熟悉的读者,我们来举个例子,如图所示:

HTML 代码

1
2
3
4
5
6
7
8
9
10
11
12
<div class="container">
<h1 class="item">Penne with tomato sauce</h1>
<p class="item">This simple recipe has few ingredients but tastes delicious.</p>
<div class="item ingredients">
<h2>You'll need</h2>
<ul>
<li>canned tomatoes</li>
<li>onions</li>
<li>garlic</li>
</ul>
</div>
</div>

CSS 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
.container { 
display: grid; /* element is now a grid container */
grid-template-columns: repeat( 4, 1fr ); /* grid now has 4 columns */

.item:nth-child(1) {
grid-columns: 1 / 2; /* Place item between grid line 1 and 2 */
}

.item:nth-child(2) {
grid-columns: 2 / 4; /* Place item between grid line 2 and 4 */
}

.item:nth-child(3) {
grid-columns: 4 / 5; /* Place item between line 4 and 5 */
}

我使用了 .container.item 作为元素的类名,因为他们是网格布局的核心:网格布局有一个容器和一些格子。显然,使用你自己项目里的命名约定即可。

这些元素是容器的直接子元素,所以我们可以按照网格布局的方式来排列这些元素,但当我要加入一个赞助商列表到界面上的时候,如图所示:

我们将列表加进去:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<div class="container">
<h1 class="item">Penne with tomato sauce</h1>
<p class="item">This simple recipe has few ingredients but tastes delicious.</p>
<div class="item ingredients">
<h2>You'll need</h2>
<ul>
<li>canned tomatoes</li>
<li>onions</li>
<li>garlic</li>
</ul>
</div>
<ul class="item sponsors">
<li>Supermarket 1</li>
<li>Supermarket 2</li>
</ul>
</div>

但是我们没办法按照网格的方式排列我们的赞助商。因为只有 <ul> 标签是网格容器的直接子元素,而 <li> 标签并不是,它们没有参与到网格布局中来。但如果我强行要将赞助商列表放进网格布局中又会怎么样呢?

扁平化的标签

显然,我们可以将赞助商列表的 <ul> 标签移除掉,换成使用 <div> 标签。但这么做的话,就会让标签扁平化,这样做就是顾此失彼了。

使用 <ul> 的好处很多:

  • 即使脱离了页面的上下文,它依旧是一个列表,比如在 Safari 的阅读模式里。
  • 关掉样式表的情况下,它会展示为一个列表。
  • 对于使用屏幕阅读器的用户,它是一个列表(屏幕阅读器大概会这么读: ”一个包含三个项目的列表“)。

扁平化的标签会让我们失去这些好处。

用 display: contents 解决这个问题

使用 display: contents 我们既可以保留需要的标签,又可以使用网格布局。这个属性让元素 看起来已经不存在了。它没有生成一个盒子,所以 backgrounds, borders, 其它与盒子有关的属性也不会生效。网格布局的 placement 属性也不会。但是这些都会对它的子元素生效。这个标准表明元素的行为就好像被它的子元素用 […] 覆盖了。如果听不懂可以看看这个。(译注:这篇文章介绍了 display: content 是怎样工作的)

如我们所愿,应用了 display: contents 的元素不再参与网格布局,而是它的内容参与了网格布局。所以我们的每一个赞助商出现在网格中,列表却没有。

有一些边界情况,具体参考标准

对 display: contents 的浏览器实现的可访问性考虑

对于使用无障碍阅读技术的人群,浏览器需要感知和可访问性相关的属性,元素的角色属性可以帮助无障碍阅读技术得知这些元素在页面中是什么角色。许多元素都会有内建的角色,比如列表会有给 list 的角色。

这就是为什么支持 display: contents 的浏览器会出现问题,它们不仅仅将 display: contents 解释成和布局相关的东西,还从中获得语义。这是有问题的,看看我们的赞助商例子,它的角色不再是列表,而是一些奇怪的东西。

下面我列举了在不同浏览器上的测试结果,在每一个用例中,<ul> 标签在没有设置 display: contents 的情况下,都能获得其正确的角色,反之不然。

Firefox 61

列表的角色变成了 text leafff 的 bug 描述

Chrome 66

无障碍访问无法感知到列表元素,chrome 的 bug 描述

Safari

列表没有无障碍访问的内容。Safari 的 bug 描述

<ul> 标签只是一个例子。事实上任何的元素如果使用 display: contents 这个属性都会遇到角色不能如预期的情况。

如果你热衷于 display:contents 的可访问性,可以考虑在 bug 描述上评论,赞同等。我希望浏览器能够优先处理这个。

和这个问题有关的 display 属性影响了表格的语义化,详情戳这里,和这里

结论

使用 display: contents 我们可以让非直接子元素参与到网格布局中,这让我们的标签更加语义化。标签的语义化程度决定了无障碍人群能够从我们的网页中得到多少。然而,现在浏览器对具有 display: contents 属性的标签的无障碍支持并不完善,具体表现为无法感知到标签的角色。

我认为标签在无障碍阅读中的角色不应该在收到 display: contents 的影响而消失,这样我们就得不偿失了。我已经提交了 bug 给 Firefox, chromium 和 Safari,我希望他们的可访问性在使用了 display: contents 之后原封不动,这样我们才能提供良好的布局和良好的无障碍阅读功能。