HTML 表格可访问性

在上一篇文章中,我们探讨了使 HTML 表格对视障用户可访问的最重要特性之一——<th> 元素。在本文中,我们将继续沿着这条道路,探讨更多 HTML 表格可访问性特性,例如标题/摘要、将行分组到表头、表体和表脚部分,以及设置列和行的作用域。

预备知识 HTML 基础知识(参见基本 HTML 语法)。
学习成果
  • 了解与表格相关的可访问性问题。
  • 为表格添加标题。
  • 通过表头、表体和表脚更好地构建表格。
  • 使用 scopeidheaders 属性在表头和单元格之间建立进一步的关联。

回顾:视障用户的表格

让我们简要回顾一下如何使用数据表。表格是一个方便的工具,可以让我们快速访问数据并查找不同的值。例如,只需快速浏览一下下表,就能找出 2016 年 8 月在根特售出了多少个戒指。为了理解其信息,我们会在该表中的数据与列和/或行标题之间建立视觉关联。

2016 年 8 月销售的商品
服装 配饰
裤子 裙子 连衣裙 手镯 戒指
比利时 安特卫普 56 22 43 72 23
根特 46 18 50 61 15
布鲁塞尔 51 27 38 69 28
荷兰 阿姆斯特丹 89 34 69 85 38
乌得勒支 80 12 43 36 19

但是如果你无法建立这些视觉关联呢?那么你如何阅读上面的表格呢?视障人士通常使用屏幕阅读器来为他们读出网页上的信息。当你阅读纯文本时,这不是问题,但解释表格对盲人来说可能是一个相当大的挑战。然而,通过适当的标记,我们可以用程序化的关联替换视觉关联。

注意:根据世卫组织 2017 年的数据,约有 2.53 亿人患有视力障碍。

使用列和行标题

屏幕阅读器将识别所有标题,并使用它们在这些标题与它们相关的单元格之间建立程序化的关联。列和行标题的组合将识别和解释每个单元格中的数据,以便屏幕阅读器用户能够像有视力用户一样解释表格。

我们已经在上一篇文章中介绍了标题——请参阅使用 <th> 元素添加标题

使用 <caption> 为表格添加标题

您可以通过将表格标题放在 <caption> 元素中,并将其嵌套在 <table> 元素中来为表格添加标题。您应该将其放在开启 <table> 标签的正下方。

html
<table>
  <caption>
    Dinosaurs in the Jurassic period
  </caption>

  …
</table>

正如您从上面的简短示例中可以推断出的那样,标题旨在包含表格内容的描述。这对于所有希望在浏览页面时快速了解表格是否对他们有用的读者都很有用,尤其是对于盲人用户。用户无需让屏幕阅读器读出许多单元格的内容来了解表格的内容,而是可以依靠标题,然后决定是否更详细地阅读表格。

标题直接放在 <table> 标签下方。

注意: summary 属性也可以用于 <table> 元素来提供描述——屏幕阅读器也会读出它。但是,我们建议改用 <caption> 元素,因为 summary 已弃用,并且视力正常的读者无法阅读它(它不会出现在页面上)。

表格标题练习

现在,我们将让您尝试为 HTML 表格添加标题,以语言教师的课程表为例。

  1. 在本地复制我们的 timetable-fixed.html 文件。
  2. 为表格添加一个合适的标题。
  3. 保存代码并在浏览器中打开以查看其外观。
点击此处显示解决方案

您完成的 HTML 应该看起来像这样

html
<table>
  <caption>
    Florence's weekly lesson timetable
  </caption>
  <colgroup>
    <col span="2" />
    <col style="background-color: #97DB9A;" />
    <col style="width: 42px;" />
    <col style="background-color: #97DB9A;" />
    <col style="background-color: #DCC48E; border: 4px solid #C1437A;" />
    <col span="2" style="width: 42px;" />
  </colgroup>
  <tr>
    <!-- Rest of code omitted for brevity -->
  </tr>
</table>

您可以在 GitHub 上找到此代码:timetable-caption.html也可以查看其现场运行情况)。

使用 <thead>、<tbody> 和 <tfoot> 添加结构

随着表格结构变得更加复杂,为其提供更多结构定义会很有用。一种明确的方法是使用 <thead><tbody><tfoot>,它们允许您标记表格的表头、表体和表脚部分。

这些元素不一定会使屏幕阅读器用户更容易访问表格。它们本身不会产生任何视觉增强,但它们对于通过 CSS 应用样式和布局增强非常有用,这可以提高可访问性。举一些有趣的例子,对于长表格,您可以让表格的表头和表脚在每个打印页面上重复出现,并且可以让表格主体显示在一个页面上,并通过上下滚动来查看内容。

要使用它们,它们应该按以下顺序包含

  • <thead> 元素必须包裹表格的表头部分——这通常是包含列标题的第一行,但并非总是如此。如果您正在使用 <col>/<colgroup> 元素,则表格表头应紧随其后。
  • <tbody> 元素需要包裹表格内容中除表头或表脚之外的主要部分。
  • <tfoot> 元素需要包裹表格的表脚部分——例如,这可能是一个包含前几行项目总和的最后一行。

注意: <tbody> 总是包含在每个表格中,如果您在代码中没有明确指定它,它也会隐式存在。要验证这一点,请打开您之前不包含 <tbody> 的一个示例,并在您的浏览器开发者工具中查看 HTML 代码——您会看到浏览器已为您添加了此标签。您可能想知道为什么还要费心包含它——您应该这样做,因为它为您提供了对表格结构和样式的更多控制。

为消费记录表添加结构

让我们让您将这些新元素付诸实践。

  1. 首先,在新的文件夹中复制 spending-record.htmlminimal-table.css 的本地副本。

  2. 尝试将明显的标题行放在 <thead> 元素内,将“SUM”行放在 <tfoot> 元素内,并将其余内容放在 <tbody> 元素内。

  3. 接下来,添加一个 colspan 属性,使“SUM”单元格跨越前四列,以便实际数字出现在“成本”列的底部。

  4. 让我们为表格添加一些简单的额外样式,让您了解这些元素对于应用 CSS 是多么有用。在 HTML 文档的头部,您会看到一个空的 <style> 元素。在此元素内部,添加以下 CSS 代码行

    css
    tbody {
      font-size: 95%;
      font-style: italic;
    }
    
    tfoot {
      font-weight: bold;
    }
    

    注意:我们不期望您现在完全理解 CSS。您将在学习我们的 CSS 模块时了解更多信息(从CSS 样式基础开始,其中包括一篇专门针对表格样式的文章)。

  5. 保存并刷新,然后查看结果。如果 <tbody><tfoot> 元素不存在,您将不得不编写更复杂的选择器/规则来应用相同的样式。

点击此处显示解决方案

您完成的 HTML 应该看起来像这样

html
<table>
  <caption>
    How I chose to spend my money
  </caption>
  <thead>
    <tr>
      <th>Purchase</th>
      <th>Location</th>
      <th>Date</th>
      <th>Evaluation</th>
      <th>Cost (€)</th>
    </tr>
  </thead>
  <tfoot>
    <tr>
      <td colspan="4">SUM</td>
      <td>118</td>
    </tr>
  </tfoot>
  <tbody>
    <tr>
      <td>Haircut</td>
      <td>Hairdresser</td>
      <td>12/09</td>
      <td>Great idea</td>
      <td>30</td>
    </tr>
    <tr>
      <td>Lasagna</td>
      <td>Restaurant</td>
      <td>12/09</td>
      <td>Regrets</td>
      <td>18</td>
    </tr>
    <tr>
      <td>Shoes</td>
      <td>Shoe shop</td>
      <td>13/09</td>
      <td>Big regrets</td>
      <td>65</td>
    </tr>
    <tr>
      <td>Toothpaste</td>
      <td>Supermarket</td>
      <td>13/09</td>
      <td>Good</td>
      <td>5</td>
    </tr>
  </tbody>
</table>

您可以在 GitHub 上找到完整的代码:spending-record-finished.html也可以查看其现场运行情况)。

scope 属性

scope 属性可以添加到 <th> 元素中,以精确告诉屏幕阅读器该标题是哪个单元格的标题——例如,它是其所在行的标题还是列的标题?回顾我们之前消费记录的例子,您可以像这样明确地将列标题定义为列标题

html
<thead>
  <tr>
    <th scope="col">Purchase</th>
    <th scope="col">Location</th>
    <th scope="col">Date</th>
    <th scope="col">Evaluation</th>
    <th scope="col">Cost (€)</th>
  </tr>
</thead>

并且每行都可以像这样定义标题(如果我们同时添加行标题和列标题)

html
<tr>
  <th scope="row">Haircut</th>
  <td>Hairdresser</td>
  <td>12/09</td>
  <td>Great idea</td>
  <td>30</td>
</tr>

屏幕阅读器将识别这种结构化的标记,并允许用户一次性读出整个列或行,例如。

scope 还有另外两个可能的值——colgrouprowgroup。这些用于位于多个列或行之上的标题。如果您回顾本文开头“2016 年 8 月销售商品”表格,您会看到“服装”单元格位于“裤子”、“裙子”和“连衣裙”单元格的上方。所有这些单元格都应标记为标题(<th>),但“服装”是一个位于其上方并定义其他三个子标题的标题。“服装”因此应获得 scope="colgroup" 属性,而其他标题将获得 scope="col" 属性

html
<thead>
  <tr>
    <th colspan="3" scope="colgroup">Clothes</th>
  </tr>
  <tr>
    <th scope="col">Trousers</th>
    <th scope="col">Skirts</th>
    <th scope="col">Dresses</th>
  </tr>
</thead>

同样适用于多个分组行的标题。再次查看“2016 年 8 月销售的商品”表格,这次重点关注带有“阿姆斯特丹”和“乌得勒支”标题(<th>)的行。您会注意到“荷兰”标题(也标记为 <th> 元素)跨越了两行,是另外两个子标题的标题。因此,应在此标题单元格上指定 scope="rowgroup",以帮助屏幕阅读器建立正确的关联

html
<tr>
  <th rowspan="2" scope="rowgroup">The Netherlands</th>
  <th scope="row">Amsterdam</th>
  <td>89</td>
  <td>34</td>
  <td>69</td>
</tr>
<tr>
  <th scope="row">Utrecht</th>
  <td>80</td>
  <td>12</td>
  <td>43</td>
</tr>

idheaders 属性

除了使用 scope 属性之外,另一种方法是使用 idheaders 属性来创建数据单元格和标题单元格之间的关联。

<th> 元素可以为数据单元格 (<td>) 提供标题,在更复杂的表格中,还可以为另一个标题单元格 (<th>) 提供标题。这允许您创建分层或分组标题,其中一个标题描述了其他几个标题。

headers 属性用于将单元格(<td><th>)链接到一个或多个标题单元格。它接受一个以空格分隔的字符串列表;字符串的顺序无关紧要。每个字符串必须匹配与其关联的 <th> 元素的唯一 id

这种方法为您的 HTML 表格提供了对每个单元格位置更明确的定义,基于它所属的列和行的标题,有点像电子表格。为了使其良好工作,您的表格应该同时包含列标题和行标题。

让我们看看“2016 年 8 月销售商品”示例的一部分,以了解如何使用 idheaders 属性

  1. 为表格中的每个 <th> 元素添加一个唯一的 id
  2. 对于标题单元格:为每个充当副标题的 <th> 元素添加一个 headers 属性,即其上方还有另一个标题的标题单元格。该值是高级别标题的 id。在我们的示例中,列标题为 "clothes",行标题为 "belgium"
  3. 对于数据单元格:为每个 <td> 元素添加一个 headers 属性,并将相关联的 <th> 元素(一个或多个)的 id 作为以空格分隔的列表添加。您可以像在电子表格中那样进行操作:找到数据单元格,然后找到描述它的行和列标题。指定的 id 顺序无关紧要,但保持一致有助于保持代码的组织性和可读性。
html
<thead>
  <tr>
    <th></th>
    <th></th>
    <th id="clothes" colspan="3">Clothes</th>
  </tr>
  <tr>
    <th></th>
    <th></th>
    <th id="trousers" headers="clothes">Trousers</th>
    <th id="skirts" headers="clothes">Skirts</th>
    <th id="dresses" headers="clothes">Dresses</th>
  </tr>
</thead>
<tbody>
  <tr>
    <th id="belgium" rowspan="2">Belgium</th>
    <th id="antwerp" headers="belgium">Antwerp</th>
    <td headers="belgium antwerp clothes trousers">56</td>
    <td headers="belgium antwerp clothes skirts">22</td>
    <td headers="belgium antwerp clothes dresses">43</td>
  </tr>
  <tr>
    <th id="ghent" headers="belgium">Ghent</th>
    <td headers="belgium ghent clothes trousers">41</td>
    <td headers="belgium ghent clothes skirts">17</td>
    <td headers="belgium ghent clothes dresses">35</td>
  </tr>
</tbody>

在此示例中

  • "Belgium"<th> 使用 rowspan="2" 跨越了 "Antwerp""Ghent"
  • 城市标题单元格("Antwerp""Ghent")使用 headers 属性引用 "belgium" 以表明它们属于比利时组。
  • 每个 <td> 都包含国家(belgium)、城市(antwerpghent)、组(clothes)和特定服装项目(trousersskirtsdresses)的 headers 属性。

注意:这种方法在标题和数据单元格之间创建了非常精确的关联,但它使用了**大量**的标记,并且没有留下任何出错的空间。对于大多数表格来说,scope 方法通常就足够了。

使用 scope 和 headers

在这个最后的练习中,我们将让您尝试在我们上面介绍的示例表上使用 scope 和 headers。

  1. 首先,在新目录中,在本地复制 items-sold.htmlminimal-table.css
  2. 尝试添加适当的 scope 属性,以使此表格更易于访问。
  3. 在另一个本地目录中复制启动文件
  4. 这次通过使用 idheaders 属性创建精确和明确的关联,使表格更易于访问。
点击此处显示解决方案

第一个完成的 HTML 示例应该看起来像这样

html
<table>
  <caption>
    Items Sold August 2016
  </caption>
  <thead>
    <tr>
      <td colspan="2" rowspan="2"></td>
      <th colspan="3" scope="colgroup">Clothes</th>
      <th colspan="2" scope="colgroup">Accessories</th>
    </tr>
    <tr>
      <th scope="col">Trousers</th>
      <th scope="col">Skirts</th>
      <th scope="col">Dresses</th>
      <th scope="col">Bracelets</th>
      <th scope="col">Rings</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th rowspan="3" scope="rowgroup">Belgium</th>
      <th scope="row">Antwerp</th>
      <td>56</td>
      <td>22</td>
      <td>43</td>
      <td>72</td>
      <td>23</td>
    </tr>
    <tr>
      <th scope="row">Ghent</th>
      <td>46</td>
      <td>18</td>
      <td>50</td>
      <td>61</td>
      <td>15</td>
    </tr>
    <tr>
      <th scope="row">Brussels</th>
      <td>51</td>
      <td>27</td>
      <td>38</td>
      <td>69</td>
      <td>28</td>
    </tr>
    <tr>
      <th rowspan="2" scope="rowgroup">The Netherlands</th>
      <th scope="row">Amsterdam</th>
      <td>89</td>
      <td>34</td>
      <td>69</td>
      <td>85</td>
      <td>38</td>
    </tr>
    <tr>
      <th scope="row">Utrecht</th>
      <td>80</td>
      <td>12</td>
      <td>43</td>
      <td>36</td>
      <td>19</td>
    </tr>
  </tbody>
</table>

而第二个应该看起来像这样

html
<table>
  <caption>
    Items Sold August 2016
  </caption>
  <thead>
    <tr>
      <td colspan="2" rowspan="2"></td>
      <th colspan="3" id="clothes">Clothes</th>
      <th colspan="2" id="accessories">Accessories</th>
    </tr>
    <tr>
      <th id="trousers" headers="clothes">Trousers</th>
      <th id="skirts" headers="clothes">Skirts</th>
      <th id="dresses" headers="clothes">Dresses</th>
      <th id="bracelets" headers="accessories">Bracelets</th>
      <th id="rings" headers="accessories">Rings</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th rowspan="3" id="belgium">Belgium</th>
      <th id="antwerp" headers="belgium">Antwerp</th>
      <td headers="antwerp belgium clothes trousers">56</td>
      <td headers="antwerp belgium clothes skirts">22</td>
      <td headers="antwerp belgium clothes dresses">43</td>
      <td headers="antwerp belgium accessories bracelets">72</td>
      <td headers="antwerp belgium accessories rings">23</td>
    </tr>
    <tr>
      <th id="ghent" headers="belgium">Ghent</th>
      <td headers="ghent belgium clothes trousers">46</td>
      <td headers="ghent belgium clothes skirts">18</td>
      <td headers="ghent belgium clothes dresses">50</td>
      <td headers="ghent belgium accessories bracelets">61</td>
      <td headers="ghent belgium accessories rings">15</td>
    </tr>
    <tr>
      <th id="brussels" headers="belgium">Brussels</th>
      <td headers="brussels belgium clothes trousers">51</td>
      <td headers="brussels belgium clothes skirts">27</td>
      <td headers="brussels belgium clothes dresses">38</td>
      <td headers="brussels belgium accessories bracelets">69</td>
      <td headers="brussels belgium accessories rings">28</td>
    </tr>
    <tr>
      <th rowspan="2" id="netherlands">The Netherlands</th>
      <th id="amsterdam" headers="netherlands">Amsterdam</th>
      <td headers="amsterdam netherlands clothes trousers">89</td>
      <td headers="amsterdam netherlands clothes skirts">34</td>
      <td headers="amsterdam netherlands clothes dresses">69</td>
      <td headers="amsterdam netherlands accessories bracelets">85</td>
      <td headers="amsterdam netherlands accessories rings">38</td>
    </tr>
    <tr>
      <th id="utrecht" headers="netherlands">Utrecht</th>
      <td headers="utrecht netherlands clothes trousers">80</td>
      <td headers="utrecht netherlands clothes skirts">12</td>
      <td headers="utrecht netherlands clothes dresses">43</td>
      <td headers="utrecht netherlands accessories bracelets">36</td>
      <td headers="utrecht netherlands accessories rings">19</td>
    </tr>
  </tbody>
</table>

您还可以在 GitHub 上找到完整的示例

总结

关于 HTML 中的表格,您还可以学习其他一些知识,但目前您只需要了解这些。接下来,您可以尝试我们的 HTML 表格挑战。玩得开心!