为移动设备设计一个 table 和两个 headers 折叠的样式

Style a table with two headers collapsed for mobile

w3c wcag tutorials

中的两个headerstable为例

table {
  border-collapse: collapse;
  border-spacing: 0
}

table th {
  text-align: left;
  background-color: #ccc
}

table th,
table td {
  padding: .5em;
  border: 1px solid #999
}
<table>
  <tr>
    <td></td>
    <th>Monday</th>
    <th>Tuesday</th>
    <th>Wednesday</th>
    <th>Thursday</th>
    <th>Friday</th>
  </tr>
  <tr>
    <th>09:00 - 11:00</th>
    <td>Closed</td>
    <td>Open</td>
    <td>Open</td>
    <td>Closed</td>
    <td>Closed</td>
  </tr>
  <tr>
    <th>11:00 - 13:00</th>
    <td>Open</td>
    <td>Open</td>
    <td>Closed</td>
    <td>Closed</td>
    <td>Closed</td>
  </tr>
  <tr>
    <th>13:00 - 15:00</th>
    <td>Open</td>
    <td>Open</td>
    <td>Open</td>
    <td>Closed</td>
    <td>Closed</td>
  </tr>
  <tr>
    <th>15:00 - 17:00</th>
    <td>Closed</td>
    <td>Closed</td>
    <td>Closed</td>
    <td>Open</td>
    <td>Open</td>
  </tr>
</table>

是否可以在不更改标记的情况下对其进行样式设置(它必须保持可访问),以便在响应式移动视图中它折叠成垂直 table 像这样:

星期一

09:00 - 11:00 关闭

11:00 - 13:00 打开

13:00 - 15:00 打开

15:00 - 17:00 关闭

星期二

09:00 - 11:00 打开

11:00 - 13:00 打开

13:00 - 15:00 打开

15:00 - 17:00 关闭

等等

我根据蚂蚁的评论做了一个回答,给别人一些可以用来获得更好或有效答案的东西:

包含在下面的代码段中,

这还不是一帆风顺,只是思绪,未完成。随时改进它。

// data attribute generated from the th content of each tr
for (let tr of document.querySelectorAll("tr")) {
  var myDataAttr = tr.querySelector("th").textContent;
  for (let td of tr.querySelectorAll("td")) {
    td.setAttribute("data-time", myDataAttr);
  }
}

// check if display:contents is avalaible
var supported = false;
if (window.CSS) {
    supported = window.CSS.supports('display', 'contents');
} else {
    //nothing needed for now
}


// check screen size for the role attributes on td's 
window.onload = mymq;
window.onresize = mymq;

function mymq() {
  const mq = window.matchMedia("(max-width: 768px)");
  if (mq.matches  &&  supported !=false) {// also checking on display:contents supports
    for (let td of document.querySelectorAll("td")) {
      td.setAttribute("role", "row");
    }
  } else {
    for (let td of document.querySelectorAll("td")) {
      td.setAttribute("role", "cell");
    }
  }
}
table {
  width: 100%;
  border-collapse: collapse;
  background: rgb(196, 215, 70)
}

tr:nth-child(2n) {
  background: lightblue;
}

th,
:before {
  background: tomato;
  box-shadow: inset 0 0 0 2px;
}

th,
td {
  box-shadow: inset 0 0 0 2px;
  text-align: center;
  vertical-align: middle;
  padding: 0.5em;
  vertical-align: middle;
}

@supports (display: contents) {
  /* trick works if data-time attributes stands in html and if display:contents is supported */
  @media screen and (max-width: 768px) {
    table {
      display: flex;
      flex-flow: column;
    }
    thead,
    tr,
    tbody {
      display: contents;
    }
    tr th:first-child {
      display: none;
    }
    th {
      background: red;
    }
    td {
      display: table;
      table-layout: fixed;
      border-collapse: collapse;
      width: 100%;
    }
    td:before {
      content: attr(data-time);
      border-right: solid 1px;
      display: table-cell;
      vertical-align: middle;
      white-space: pre;
      /* only if you care */
      padding: 0.25em;
    }
    tr :nth-child(2) {
      order: 0;
    }
    tr :nth-child(3) {
      order: 1;
    }
    tr :nth-child(4) {
      order: 2;
    }
    tr :nth-child(5) {
      order: 3;
    }
    tr :nth-child(6) {
      order: 4;
    }
    tr :nth-child(7) {
      order: 5;
    }
  }
}

/* let's see if role attribute value is being updated  */
td::after {
  content:'role='attr(role);
  display:block;
  font-family:courier;
  font-size:0.7em;
}
<table role="table">
  <caption>Delivery slots:</caption>
  <tr>
    <td></td>
    <th scope="col">Monday</th>
    <th scope="col">Tuesday</th>
    <th scope="col">Wednesday</th>
    <th scope="col">Thursday</th>
    <th scope="col">Friday</th>
  </tr>
  <tr>
    <th scope="row">09:00 - 11:00</th>
    <td>Closed</td>
    <td>Open</td>
    <td>Open</td>
    <td>Closed</td>
    <td>Closed</td>
  </tr>
  <tr>
    <th scope="row">11:00 - 13:00</th>
    <td>Open</td>
    <td>Open</td>
    <td>Closed</td>
    <td>Closed</td>
    <td>Closed</td>
  </tr>
</table>

不是一个完整的答案,因为我会为每个场景制作小提琴,只是想把思考过程记下来供你玩 - 更新:完成 3 / 4 个小提琴

我在这里玩了很多选项,有趣的挑战!

简答

对您的问题的简短回答是否定的,您不能只使用 CSS.CSS.

保留给定的结构,使其具有响应性并保持其可访问性

我敢说不可能按照你的要求去做,因为这会涉及使用某种会破坏可访问性树的显示属性(即如果我们立即使用 display: block破坏了辅助功能树)。

我什至考虑过使用 position: absolute(一个糟糕的想法,但想看看是否可以实现),但这也会立即破坏可访问性树。

由于数据的结构,我认为使用 CSS 执行此操作的唯一方法只涉及数百个 :before:aftercontent: CSS 属性/选择器只会是一团糟。因此,我没有探索该选项,但对于有足够决心完成这项工作的人来说,这种方式是可能的。

为什么这不可能?

我认为答案预示着网页设计师使用 tables 进行布局的美好时光。第二次应用任何定位时,浏览器假定您正在使用 table 进行布局,因此将其从可访问性树中删除(因为如果留在可访问性树中和 table 用于布局!)

那有什么选择呢?

我有几种不同的方法可以实现最终结果。我会尝试权衡每一项的利弊,但每一项都至少违反了您的一项要求。

选项 1. 为移动视图创建第二个 table 并使用媒体查询将它们关闭。

通过创建第二个 table,您可以针对移动设备与 tablet 和桌面设备以不同方式呈现信息。

我很惊讶我发现这种方法有多少积极因素。

  • 它允许您保持原始标记(尽管您必须为第二个标记添加标记 table)
  • 关闭 tables 实际上是媒体查询中的一个 CSS 选择器(display: nonedisplay: table)。
  • 可访问性将保持不变(假设您正确构建第二个 table)
  • 性能 - 我实际上认为添加第二个 table 对加载时间不利,但经过一段时间后我意识到它可能会产生很小的影响,因为减少的 CSS 要求会抵消增加了 HTML,加上 tables 使用 GZIP 等压缩很好
  • 不需要任何 JavaScript(但需要在后端以编程方式或手动方式创建 table 两次)

选项 1 示例

(桌面全屏/原始视图)

我在构建此示例时的想法 - 我实际上不确定以下 table 在语义上是否正确。在 headers 和 table 中的页脚/第一列以外的任何地方添加 <th> 有点不寻常。它通过了验证并且在屏幕上看起来没问题 reader 但您仍然可能想自己检查一下。

table {
  border-collapse: collapse;
  border-spacing: 0
}

table th {
  text-align: left;
  background-color: #ccc
}

table th,
table td {
  padding: .5em;
  border: 1px solid #999
}


/*start with mobile view hidden*/
table:nth-child(2){
  display:none;
}

@media only screen and (max-width: 720px) {
/*toggle second table in for mobile view*/
    table:nth-child(1){
  display:none;
}
table:nth-child(2){
  display:table;
}
}
<table>
  <tr>
    <td></td>
    <th>Monday</th>
    <th>Tuesday</th>
    <th>Wednesday</th>
    <th>Thursday</th>
    <th>Friday</th>
  </tr>
  <tr>
    <th>09:00 - 11:00</th>
    <td>Closed</td>
    <td>Open</td>
    <td>Open</td>
    <td>Closed</td>
    <td>Closed</td>
  </tr>
  <tr>
    <th>11:00 - 13:00</th>
    <td>Open</td>
    <td>Open</td>
    <td>Closed</td>
    <td>Closed</td>
    <td>Closed</td>
  </tr>
  <tr>
    <th>13:00 - 15:00</th>
    <td>Open</td>
    <td>Open</td>
    <td>Open</td>
    <td>Closed</td>
    <td>Closed</td>
  </tr>
  <tr>
    <th>15:00 - 17:00</th>
    <td>Closed</td>
    <td>Closed</td>
    <td>Closed</td>
    <td>Open</td>
    <td>Open</td>
  </tr>
</table>

<table>
<tr>
  <th colspan="2">Monday</th>
</tr>
<tr>
  <td>09:00 - 11:00</td>
  <td>Closed</td>
</tr>
<tr>
  <td>11:00 - 13:00</td>
  <td>Open</td>
</tr>
<tr>
  <td>13:00 - 15:00</td>
  <td>Open</td>
</tr>
<tr>
  <td>15:00 - 17:00</td>
  <td>Closed</td>
</tr>
<tr>
  <th colspan="2">Tuesday</th>
</tr>
<tr>
  <td>09:00 - 11:00</td>
  <td>Open</td>
</tr>
<tr>
  <td>11:00 - 13:00</td>
  <td>Open</td>
</tr>
<tr>
  <td>13:00 - 15:00</td>
  <td>Open</td>
</tr>
<tr>
  <td>15:00 - 17:00</td>
  <td>Closed</td>
</tr>
<tr>
  <th colspan="2">Wednesday</th>
</tr>
<tr>
  <td>09:00 - 11:00</td>
  <td>Open</td>
</tr>
<tr>
  <td>11:00 - 13:00</td>
  <td>Closed</td>
</tr>
<tr>
  <td>13:00 - 15:00</td>
  <td>Open</td>
</tr>
<tr>
  <td>15:00 - 17:00</td>
  <td>Closed</td>
</tr>
<tr>
  <th colspan="2">Thursday</th>
</tr>
<tr>
  <td>09:00 - 11:00</td>
  <td>Closed</td>
</tr>
<tr>
  <td>11:00 - 13:00</td>
  <td>Closed</td>
</tr>
<tr>
  <td>13:00 - 15:00</td>
  <td>Closed</td>
</tr>
<tr>
  <td>15:00 - 17:00</td>
  <td>Open</td>
</tr>
<tr>
  <th colspan="2">Friday</th>
</tr>
<tr>
  <td>09:00 - 11:00</td>
  <td>Closed</td>
</tr>
<tr>
  <td>11:00 - 13:00</td>
  <td>Closed</td>
</tr>
<tr>
  <td>13:00 - 15:00</td>
  <td>Closed</td>
</tr>
<tr>
  <td>15:00 - 17:00</td>
  <td>Open</td>
</tr>
</table>

选项 2。只需让 table 在移动设备上滚动即可。

另一个在纸面上看起来是个不错的选择,但我就是不喜欢。 table 滚动是我能想到的最糟糕的移动体验之一(海量数据集除外)。

这也是一个有问题的可访问性点,屏幕 readers 可以,但是对于那些有准确性问题的人来说,可滚动的 table 是否可以访问?可能不是。

选项 2 示例

只需将屏幕设置为小于 720 像素即可看到水平滚动时出现的滚动条。

table {
  border-collapse: collapse;
  border-spacing: 0;
  min-width: 720px;
}

table th {
  text-align: left;
  background-color: #ccc
}

table th,
table td {
  padding: .5em;
  border: 1px solid #999
}

DIV{
  overflow-x:auto;
}
<div>
<table>
  <tr>
    <td></td>
    <th>Monday</th>
    <th>Tuesday</th>
    <th>Wednesday</th>
    <th>Thursday</th>
    <th>Friday</th>
  </tr>
  <tr>
    <th>09:00 - 11:00</th>
    <td>Closed</td>
    <td>Open</td>
    <td>Open</td>
    <td>Closed</td>
    <td>Closed</td>
  </tr>
  <tr>
    <th>11:00 - 13:00</th>
    <td>Open</td>
    <td>Open</td>
    <td>Closed</td>
    <td>Closed</td>
    <td>Closed</td>
  </tr>
  <tr>
    <th>13:00 - 15:00</th>
    <td>Open</td>
    <td>Open</td>
    <td>Open</td>
    <td>Closed</td>
    <td>Closed</td>
  </tr>
  <tr>
    <th>15:00 - 17:00</th>
    <td>Closed</td>
    <td>Closed</td>
    <td>Closed</td>
    <td>Open</td>
    <td>Open</td>
  </tr>
</table>
</div>

选项 3. 根本不使用 table 而是使用不同的标记并使其看起来像 table.

这似乎是个愚蠢的想法,但在很多方面确实如此! CSS 将是一项艰苦的工作,而且它在语义上不正确。

尽管标记最终在语义上是正确的(从文档结构的角度来看),因为您有一个“标题”(<h2>Monday</h2>),然后是一个“sub-heading”(<h3>09:00 - 11:00</h3>) 后面是相关信息 <p>Closed</p>。这只是意味着视觉样式与 HTML 信息不完全相同。

此方法的唯一优点是我们不必依赖 WAI-ARIA 来使其工作,并且我们可以在不影响屏幕 reader 的情况下以视觉方式放置我们想要的东西。 WAI-ARIA 并非 100% 完美支持,因此这可能是所有解决方案中最容易获得的。不利的一面是它完全改变了你所加星标的数据编着。

选项 3 示例

请比我做得更好CSS,但原则是正确的。

请注意我如何在 'desktop' 视图的开头设置 aria-hidden 部分(将其从屏幕 reader 中隐藏,但不在视觉上显示)以及我如何使用 [= .col h3 选择器上的 32=]。这样就屏幕 reader 而言 HTML 两者完全相同。

.container{
  width: 60%;
}


@media only screen and (min-width: 1024px) {
h2, .desktop h3, p{
  outline: 1px solid #333;
  outline-offset: -1px;
  padding: 0px 10px;
  margin: 0;
}


div{
  float: left;
  width: 15%;
  line-height: 2rem;
}
h2{
  line-height: 3rem;

}
.col h3{
  position: absolute !important;
    height: 1px; 
    width: 1px;
    overflow: hidden;
    clip: rect(1px 1px 1px 1px); /* IE6, IE7 */
    clip: rect(1px, 1px, 1px, 1px);
    white-space: nowrap; /* added line */
}
.desktop{
  width: 25%;
  margin-top: 3rem;
  float:left;
  display: block;
}
.desktop h3{
  display: block;
  line-height: 2rem;
}

}

@media only screen and (max-width: 1023px) {
  .desktop{
    display: none;
  }
  h2{
    width: 100%;
    display: block;
    float: left;
    padding: 10px 0px 10px 0px;
    margin: 5px 0;
    border-top: 1px solid #666;
    border-bottom: 1px solid #666;
  }
  h3{
    width: 50%;
    display: block;
    float:left;
    line-height: 2rem;
     padding: 0px;
    margin: 0;
  }
  .col p{
    width: 50%;
    display: block;
    float:left;
    line-height: 2rem;
     padding: 0px;
    margin: 0;
  }
  
  
  
}
<div class=container>
<div class="desktop" aria-hidden="true">
  <h3>09:00 - 11:00</h3>
  <h3>11:00 - 13:00</h3>
  <h3>13:00 - 15:00</h3>
  <h3>15:00 - 17:00</h3>
</div>
<div class="col">
<h2>Monday</h2>
<h3>09:00 - 11:00</h3>
<p>Closed</p>
<h3>11:00 - 13:00</h3>
<p>Open</p>
<h3>13:00 - 15:00</h3>
<p>Open</p>
<h3>15:00 - 17:00</h3>
<p>Closed</p>
</div>
<div class="col">
<h2>Tuesday</h2>
<h3>09:00 - 11:00</h3>
<p>Open</p>
<h3>11:00 - 13:00</h3>
<p>Open</p>
<h3>13:00 - 15:00</h3>
<p>Open</p>
<h3>15:00 - 17:00</h3>
<p>Closed</p>
</div>
<div class="col">
<h2>Wednesday</h2>
<h3>09:00 - 11:00</h3>
<p>Open</p>
<h3>11:00 - 13:00</h3>
<p>Closed</p>
<h3>13:00 - 15:00</h3>
<p>Open</p>
<h3>15:00 - 17:00</h3>
<p>Closed</p>
</div>
<div class="col">
<h2>Thursday</h2>
<h3>09:00 - 11:00</h3>
<p>Closed</p>
<h3>11:00 - 13:00</h3>
<p>Closed</p>
<h3>13:00 - 15:00</h3>
<p>Closed</p>
<h3>15:00 - 17:00</h3>
<p>Open</p>
</div>
<div class="col">
<h2>Friday</h2>
<h3>09:00 - 11:00</h3>
<p>Closed</p>
<h3>11:00 - 13:00</h3>
<p>Closed</p>
<h3>13:00 - 15:00</h3>
<p>Closed</p>
<h3>15:00 - 17:00</h3>
<p>Open</p>
</div>
</div>

选项4.使用另一种方法(即G-Cyrillus的建议)并使用WAI-ARIA来保持可访问性。

我真的很喜欢用户 G-Cyrillus 建议的方法在关注点分离方面的简洁性。 CSS视觉,HTML语义,这是我每天的目标。

如果不是因为这个浏览器错误(现在我认为它可能不是错误,因为我之前说过为什么你不能在 tables 上使用显示属性)这个解决方案将是完美的(而且 90% coverage on display:contents 对我来说有点低,但这是由于 space 我在其中操作,我相信会有一个 suitable poly-fill 垃圾浏览器!)

对于此方法,解决方案是利用 WAI-ARIA.

这允许将属性添加到元素以添加更多信息或向屏幕描述元素的语义reader。这将帮助我们替换在将 positiondisplay 样式应用于 table.

后被破坏的语义信息

tables we have several aria items we can use to create a table from non-table elements.

此方法唯一真正的缺点是您要么必须使用 JavaScript 添加 aria 属性(不太可取),要么将它们添加到 HTML 标记中(更好那么即使 JavaScript 失败,它仍然可以完全访问)。

那么我会选择哪种方式呢?

我很惊讶我会说这个但是选项 1。“为移动视图创建第二个 table 并使用媒体查询将它们关闭。”

在可维护性方面,我假设您会以编程方式构建此链接到 CMS 或类似内容,所以我认为这不是问题。它是可访问的(假设 table 2 结构正确)并且需要最少的 CSS.

除了我的直觉反应,这是一个 hack,我实际上认为这是一个很好的选择,它将是最容易实现的方式。

当我有机会把所有的小提琴放在一起时,你可以自己选择,但我希望这同时能给你一些启发。