Skip to content
This page has been auto-translated and may contain errors.View in English

表格

docs.scrimba.com

某些信息是网格形式的。火车时刻表、定价计划、联赛排名、营养成分信息:每一个都有行和列,其含义在于它们如何排列。HTML 有一套专门为这类内容设计的元素,使用它们可以使网格在屏幕上和对页面进行语音阅读的软件中都保持可读性。

表格基础

**表格**是一个行和列的网格,形状与电子表格相同。你可以用四个元素来构建它。<table> 包装整个网格。每个 <tr> 是一行(tr 是 table row 的缩写)。在一行内,每个 <td> 是一个数据单元格(td 是 table data 的缩写)。当单元格标记其列或行时,你使用 <th>(th 是 table header 的缩写),浏览器会以粗体和居中的方式显示它,使其与数据区别开来。

你按行逐行构建表格,从不按列构建。<table> 是容器,每个 <tr> 是一行,单元格在行内按照它们在页面上出现的顺序排列。数据单元格是 <td>;**标题单元格**是 <th>没有列元素。列是每行保持其单元格顺序一致的产生效果,所以如果一行偏离步骤,网格就会不对齐。

<th><td> 之间的区别不仅仅是外观。<th> 将单元格标记为它所在行或列的标题。浏览器的默认样式(粗体和居中)只是这个含义的视觉部分。另一部分是其他软件可以理解的含义。

表格的源代码是**行优先的**:你一次写一行,每个单元格的列完全由其在行内的位置决定。浏览器读取这个行优先的源代码,并从中构建一个二维网格,通过计数匹配单元格到列中。标记中没有命名列的内容,因此网格的对齐程度取决于你的行的一致性。

<th><td> 是一个语义区分,而不是样式区分。<th>(标题单元格)告诉浏览器和任何读取页面的辅助技术(例如屏幕阅读器等软件,它为看不到页面的人大声朗读页面),这个单元格是周围数据的标签。默认的粗体和居中外观是附带的:用 CSS 剥离它,标题仍然意味着"标题",因为含义在元素中,而不是在外观中。这就是使用真实表格而不是以其他方式绘制网格的全部原因。

html
<table>
  <tr>
    <th>星期</th>
    <th>营业时间</th>
  </tr>
  <tr>
    <td>星期一</td>
    <td>8:00 到 18:00</td>
  </tr>
  <tr>
    <td>星期六</td>
    <td>9:00 到 14:00</td>
  </tr>
</table>

顶部有两个标题单元格,然后是两行数据。浏览器绘制网格,调整每列的大小以适应其内容,并以粗体呈现两个 <th> 单元格。

Juno表格基础 四个元素组成一个网格:<table> 包装整个内容,<tr> 用于每一行,<td> 用于一个单元格,<th> 用于标题单元格。标题单元格以粗体和居中的方式显示,使其与数据区别开来。按行逐行构建,列会自动对齐。
Juno表格基础 你使用 <tr> 按行布局表格,列从每行保持其单元格顺序一致而产生。<th> 用于标题,<td> 用于数据,这个选择是有含义的,不仅仅是粗体外观。如果一行中单元格数量计算错误,整个网格会弯曲。
Juno表格基础 标记是行优先的,所以列之所以存在是因为每行保持其单元格的步调一致。数清楚。<th> 之所以值得存在是因为它告诉屏幕阅读器单元格是一个标签,这是 CSS 加粗无法做到的。那个语义分裂是使用真实表格的原因。

表格结构

你的行的顺序是人们阅读它们的顺序,所以把**标题行**(由 <th> 单元格构建的那一行)放在顶部。然后保持每一行的长度相同:如果标题行有三个单元格,它下面的每个 <tr> 也需要三个 <td> 单元格。缺少单元格的行在网格中留下空隙,列就停止对齐。这里没有新东西要学,只需要小心处理你已经知道的行。

一旦表格增长,将其行分组到**行组**中,使结构明确。将标题行包装在 <thead> 中,数据行包装在 <tbody> 中,任何总结行(如总计行)包装在 <tfoot> 中。这些组本身不会改变布局很多,但它们为样式、打印和辅助软件标记表格的部分。

使用 <caption> 为表格提供标题,将其放在 <table> 的第一个子元素。它在网格上方呈现,与不同的是,它在标记中与表格绑定在一起。

当单元格需要覆盖多于一列或一行时,使用 colspanrowspancolspan="2" 使单元格与两列一样宽;rowspan="2" 使其与两行一样高。它覆盖的位置从那些行中删除,因为空间已被占用:

html
<thead>
  <tr>
    <th rowspan="2">地区</th>
    <th colspan="2">2024</th>
  </tr>
  <tr>
    <th>Q1</th>
    <th>Q2</th>
  </tr>
</thead>

"地区"填充两个标题行,"2024"填充两列,Q1 和 Q2 在第二行的其下方。

行组不是装饰。<thead><tfoot> 在打印长表格时定义为在每一页上重复,因此标题在页面分隔处保持可见,它们让你在 <tbody> 滚动时保持固定的标题或页脚。一个表格可能包含多个 <tbody> 组,这是将长数据集分成标记部分的一个干净方法,而不会破坏单个网格。

colspanrowspan 控制**单元格跨越**(一个单元格覆盖多于一列或一行),这是表格最常静默中断的地方。每一行仍必须占用网格的完整宽度。一个带 colspan="2" 的单元格填充两列,所以它重叠的行必须让那些位置为空,而不是提供它们自己的单元格。数字计算错误,浏览器仍然会呈现某些内容,但不是你想要的网格。<caption> 是表格的无障碍名称:辅助软件(例如为页面朗读的屏幕阅读器等工具)在读者到达表格时宣布它,完成附近标题无法完成的工作。

html
<table>
  <caption>按地区分的季度收入</caption>
  <thead>
    <tr>
      <th>地区</th>
      <th>Q1</th>
      <th>Q2</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>北方</th>
      <td>£12,000</td>
      <td>£15,400</td>
    </tr>
    <tr>
      <th>南方</th>
      <td>£9,800</td>
      <td>£11,200</td>
    </tr>
  </tbody>
  <tfoot>
    <tr>
      <th>总计</th>
      <td>£21,800</td>
      <td>£26,600</td>
    </tr>
  </tfoot>
</table>
Juno表格结构 行从上到下阅读,所以以你的 <th> 单元格的标题行开头。然后保持每一行的长度相同:顶部三个标题意味着下面每一行有三个 <td> 单元格。缺少的单元格是网格中的空隙,这是早期让人困惑的东西。
Juno表格结构 一旦表格增长,将部分分组:<thead> 用于标题行,<tbody> 用于数据,<tfoot> 用于总计行,<caption> 作为第一个子元素用于标题。colspanrowspan 跨列或行拉伸单元格,但请记住被覆盖的位置从那些行中删除。数字计算正确,网格就能保持。
Juno表格结构 这些组拉动真实权重:<thead><tfoot> 在打印表格时重复,可以锚定滚动的正文,<caption> 是屏幕阅读器读出的名称。跨越是安静的陷阱,因为每一行仍然必须填充完整的网格宽度,所以被覆盖的位置不获得单元格。浏览器愉快地呈现一个数字计算错误,这正是为什么它很难发现。

无障碍表格

无障碍表格是指对每个人都有意义的表格,包括用**屏幕阅读器**(为页面朗读的软件)听页面而不是看页面的人。你可以做的最有用的事情已经在你的工具箱中:对每个标题单元格使用 <th>,而不是 <td>屏幕阅读器然后可以告诉听众哪些单元格是标签,哪些是数据,所以像"£15"这样的值与它所属的列一起被宣布。一个每个单元格都是纯 <td> 的表格读起来像一堵数字的平坦墙,没有任何东西来锚定它们。

<th> 将单元格标记为标题,但在具有列标题和行标题的表格中,它不说明哪一个。scope 属性明确地说明了这一点。scope="col" 说一个标题标记其整个列;scope="row" 说它标记其整个行。将其添加到每个 <th>,屏幕阅读器可以立即将任何数据单元格与正确的列标题和行标题配对。

html
<th scope="col">月份</th>
<th scope="row">降雨量</th>

对于简单网格,浏览器通常可以猜测这些关联,但用 scope 声明它们消除了猜测,而且很容易添加。

scope 涵盖了行标题和列标题只有一个级别的表格。复杂表格,即具有跨越多列或两层列标题的标题的表格,即使使用 scope 也保持模糊,因为数据单元格不再在其上方具有恰好一个列标题和一个行标题。对于那些,用**id 和 headers** 明确地关联单元格。给每个标题单元格一个 id(该元素的唯一名称),然后在每个数据单元格的 headers 属性中列出相关的 id:

html
<th id="q1">Q1</th>
<th id="north">北方</th>
<td headers="north q1">£12,000</td>

数据单元格现在命名了准确描述它的标题,按照应该阅读的顺序,没有算法必须推断关系。它很冗长,所以将其保留给真正需要它的表格。首先使用 scope,只有当标题结构太不规则以至于 scope 无法表达时才升级到 idheaders

无障碍性的范围比表格更广;无障碍章节涵盖了其余的内容。

Juno无障碍表格 最大的收益零成本:对标题使用 <th>,对数据使用 <td>。屏幕阅读器依赖那个分割来读出每个值与它所属的标题一起,所以 £15 与其列一起到达,而不是单独到达。跳过它,整个表格都会当作一堵松散的数字墙。
Juno无障碍表格 在你的标题单元格上放 scopescope="col" 用于列标题,scope="row" 用于行标题。这让屏幕阅读器在两个轴上将每个数据单元格与正确的标题绑定在一起。浏览器可以为简单网格猜测,但声明它很便宜,并且消除了猜测。
Juno无障碍表格scope 处理普通的一个标题每个方式的网格。当标题跨越或堆叠,单元格在一个轴上有多个时,切换到标题上的 id 和数据单元格上的 headers 来手动配置它们。它很冗长,所以将其保留给真正需要它的表格,让 scope 处理其余的。

表格用于数据,不用于布局

有一段时间,在 CSS 能处理布局之前,人们用表格构建整个页面布局:一个 <table> 在左边放菜单,在右边放内容。视觉上有效,所以你仍然在旧页面和一些电子邮件模板中看到它。不要复制它。表格用于**表格数据**,属于行和列网格的信息。排列页面的部分是 CSS 的工作。使用表格来定位东西而不是保存数据会使页面对任何使用屏幕阅读器的人更难阅读,也更难在以后调整。

规则很短:<table> 意味着"这个内容是相关数据的网格"。当信息真的有行和列时使用它:一个时间表、一个比较、一个结果集。不要使用它来布局一个页面,不要使用它来排列不是数据的东西。CSS grid 和 flexbox 正确地进行布局,它们适应屏幕大小,并且它们保持你的 HTML 描述内容而不是描述排列。

为了获得列而使用表格是经典的滥用。如果内容是导航栏或跨页面传播的表单,它不是表格数据,即使它可以被制成看起来正确,表格也是错误的工具。

当你使用表格进行布局时,你会将辅助软件(例如为页面朗读的屏幕阅读器等工具)交给一个不是数据的数据结构。屏幕阅读器通过其尺寸宣布一个表格,并让用户逐个单元格移动,将布局搭建视为网格中的每个区域都是一个相关的值。结果是像"表格,四列,十行"这样的宣布,实际上是一个标题、一个侧栏和一篇文章。

还有一个阅读顺序问题。辅助软件和搜索引擎以**源顺序**(元素在你的 HTML 中出现的顺序)读取页面,而不是它们碰巧在屏幕上坐的顺序。布局表格迫使你的源顺序与视觉网格匹配,因此应该首先出现的内容,主文章,可能会在导航列之后结束,因为这是视觉列需要它的地方。CSS 布局打破了这种耦合:你可以在源中保持重要内容早期,仍然将其放在屏幕上的任何地方。保持表格用于数据,阅读顺序就一直是你的控制。

一个灰色地带是 HTML 电子邮件,其中较旧的邮件客户端仍然缺乏可靠的 CSS 布局,基于表格的结构仍然很常见。这是那个媒介的一个约束,而不是一个要带回网络的模式。

html
<!-- 错误:使用表格来放置侧栏旁边的文章 -->
<table>
  <tr>
    <td><nav>...</nav></td>
    <td><article>...</article></td>
  </tr>
</table>

<!-- 正确:真实元素,CSS 处理列 -->
<nav>...</nav>
<article>...</article>

这与什么是 HTML 中列出的结构和表示相同的分离:HTML 中的结构,CSS 中的布局。对于通过含义选择元素,请参见语义 HTML

Juno表格用于数据,不用于布局 表格用于属于网格的数据,相关值的行和列。它不是用来排列页面,即使旧站点以这种方式使用它。定位页面部分是 CSS 的工作,将其折叠到表格中会使页面更难大声阅读和更难改变。
Juno表格用于数据,不用于布局 只有当内容真的是数据网格时才使用 <table>:一个时间表、一个比较、一个结果列表。对于布置页面或排列非数据,使用 CSS grid 或 flexbox,它们适应屏幕并保持你的 HTML 关于内容。被压入布局职责的表格是经典错误工具。
Juno表格用于数据,不用于布局 布局表格误导屏幕阅读器:它听到数据网格并读出毫无意义的行和列。它还将你的源顺序固定到视觉网格,当源顺序是辅助技术和搜索引擎实际阅读的。保持表格用于数据,让 CSS 放置东西,阅读顺序就保持在你的控制下。HTML 电子邮件是唯一的例外,仅仅因为它的客户端卡在过去。