CSS 计数器与 A11y:`counter()` 生成的列表序号在屏幕阅读器中的可读性问题

CSS 计数器与 A11y:counter() 生成的列表序号在屏幕阅读器中的可读性问题

大家好!今天我们来深入探讨一个看似简单,但实际上在可访问性(A11y)方面却容易被忽视的 CSS 技术:计数器(Counters)。具体来说,我们将重点关注 counter() 函数生成的列表序号,以及它们在屏幕阅读器中的可读性问题。

什么是 CSS 计数器?

CSS 计数器本质上是由 CSS 管理的变量,其值可以递增,并且可以在文档中以不同的方式显示。它们通常用于自动编号列表、章节标题或其他需要按顺序排列的内容。

计数器的基本用法:

  1. counter-reset: 初始化计数器。
  2. counter-increment: 递增计数器的值。
  3. counter()counters(): 在 content 属性中显示计数器的值。

一个简单的例子:

<!DOCTYPE html>
<html>
<head>
<title>CSS 计数器示例</title>
<style>
body {
  counter-reset: section; /* 初始化名为 section 的计数器 */
}

h2::before {
  counter-increment: section; /* 每次遇到 h2 元素,计数器 section 递增 */
  content: "Section " counter(section) ": "; /* 在 h2 元素之前显示计数器的值 */
}
</style>
</head>
<body>

<h2>Introduction</h2>
<p>This is the introduction.</p>

<h2>Background</h2>
<p>This is the background.</p>

<h2>Methods</h2>
<p>These are the methods.</p>

</body>
</html>

在这个例子中,我们创建了一个名为 section 的计数器。每次遇到 h2 元素时,计数器都会递增,并且在 h2 元素之前显示计数器的值,形成类似 "Section 1: Introduction" 这样的标题。

屏幕阅读器和 CSS content 属性

屏幕阅读器是辅助技术,旨在帮助视觉障碍用户访问数字内容。它们通过解析 HTML 结构和内容,然后将其转换为语音或盲文输出。

content 属性在 CSS 中用于在元素的前后插入生成的内容。虽然它在视觉上很方便,但屏幕阅读器如何处理 content 属性中的内容,直接影响了内容的可访问性。

问题所在:

默认情况下,大多数屏幕阅读器会读取 content 属性中生成的内容,包括 counter() 函数生成的内容。这在某些情况下是有用的,但在列表序号方面,可能会导致重复和冗余。

counter() 生成的列表序号的可访问性问题

当使用 counter() 函数为列表生成序号时,通常会遇到以下问题:

  1. 重复读取: 屏幕阅读器可能会同时读取列表项的序号(由 counter() 生成)和列表项本身固有的序号(例如,ol 元素的序号)。
  2. 上下文缺失: 屏幕阅读器读取序号时,可能缺乏足够的上下文信息,导致用户难以理解序号的含义。
  3. 控制不足: 开发者对屏幕阅读器如何读取序号的控制有限。

一个具体的例子:

<!DOCTYPE html>
<html>
<head>
<title>列表计数器示例</title>
<style>
ol {
  counter-reset: item;
  list-style-type: none; /*隐藏默认列表序号*/
}

li {
  counter-increment: item;
}

li::before {
  content: counter(item) ". "; /* 使用计数器生成序号 */
  font-weight: bold;
}
</style>
</head>
<body>

<ol>
  <li>First item</li>
  <li>Second item</li>
  <li>Third item</li>
</ol>

</body>
</html>

在这个例子中,我们使用 counter() 函数为 ol 列表生成序号,并隐藏了默认的列表序号。在视觉上,列表项会显示为 "1. First item"、"2. Second item"、"3. Third item"。但是,屏幕阅读器可能会读取为 "1. First item 1. Second item 1. Third item",因为默认情况下,ol元素也会有自己的序号,而我们只是视觉上隐藏了。或者读取成 "1. First item"、"2. Second item"、"3. Third item",但缺乏上下文,用户可能不清楚 "1." "2." "3." 是什么。

问题分析:

  • 重复读取: 虽然我们隐藏了默认的列表序号,但 ol 元素仍然具有语义上的列表结构,屏幕阅读器可能会尝试推断并读取列表项的序号。尤其是在一些旧版本的屏幕阅读器中。
  • 上下文缺失: 屏幕阅读器可能只是简单地读取序号和列表项的内容,而没有明确地说明这是一个列表,以及序号与列表项之间的关系。

解决方案和最佳实践

为了解决上述问题,我们需要采取一些策略,以确保使用 counter() 生成的列表序号对屏幕阅读器用户来说是可访问的。

  1. 使用 ARIA 属性提供上下文:

    可以使用 ARIA(Accessible Rich Internet Applications)属性来增强元素的语义,并为屏幕阅读器提供额外的上下文信息。

    • aria-label: 为元素提供一个可访问的名称。
    • aria-labelledby: 指定一个元素,其内容将用作当前元素的可访问名称。
    • aria-describedby: 指定一个元素,其内容将用作当前元素的描述。

    示例:

    <!DOCTYPE html>
    <html>
    <head>
    <title>ARIA 计数器示例</title>
    <style>
    ol {
      counter-reset: item;
      list-style-type: none;
    }
    
    li {
      counter-increment: item;
    }
    
    li::before {
      content: counter(item) ". ";
      font-weight: bold;
    }
    </style>
    </head>
    <body>
    
    <ol aria-label="List of items">
      <li>First item</li>
      <li>Second item</li>
      <li>Third item</li>
    </ol>
    
    </body>
    </html>

    在这个例子中,我们使用 aria-label="List of items"ol 元素提供了一个可访问的名称。屏幕阅读器会读取 "List of items",从而为用户提供列表的上下文。

  2. 使用 ::marker 伪元素 (如果适用):

    CSS ::marker 伪元素允许你设置列表项标记的样式。虽然它主要用于视觉呈现,但在某些情况下,它可以简化列表序号的处理,并提高可访问性。

    注意: ::marker 的兼容性可能有限,特别是对于一些旧版本的浏览器。

    示例:

    <!DOCTYPE html>
    <html>
    <head>
    <title>::marker 计数器示例</title>
    <style>
    ol {
        list-style-type: decimal; /* 使用默认的十进制列表序号 */
    }
    </style>
    </head>
    <body>
    
    <ol>
      <li>First item</li>
      <li>Second item</li>
      <li>Third item</li>
    </ol>
    
    </body>
    </html>

    在这个例子中,我们直接使用了 ol 元素的默认列表序号,并使用 list-style-type: decimal; 将其设置为十进制。这样,屏幕阅读器就可以正确地读取列表序号,而无需使用 counter() 函数。

  3. 隐藏视觉上的序号,但保留语义上的列表序号 (慎用):

    如果需要完全控制列表序号的视觉呈现,并且不希望屏幕阅读器读取默认的列表序号,可以尝试隐藏视觉上的序号,但保留语义上的列表结构。这种方法需要谨慎使用,因为可能会导致屏幕阅读器用户感到困惑。

    示例:

    <!DOCTYPE html>
    <html>
    <head>
    <title>隐藏列表序号示例</title>
    <style>
    ol {
      list-style-position: inside; /*将列表标记放在内容里面*/
      overflow: hidden; /*隐藏超出内容*/
    }
    
    li {
      margin-left: -1.5em;  /*负margin向左移动元素,使其超出屏幕*/
      padding-left: 1.5em; /*增加内边距*/
    }
    </style>
    </head>
    <body>
    
    <ol>
      <li>First item</li>
      <li>Second item</li>
      <li>Third item</li>
    </ol>
    
    </body>
    </html>

    这个例子中,我们使用了一些 CSS 技巧来隐藏视觉上的列表序号,但 ol 元素仍然具有语义上的列表结构。屏幕阅读器可能会读取列表项的序号,但用户在视觉上看不到它。然后可以用counter()生成自己的序号,放在list item的内容里面。

    警告: 这种方法可能会导致屏幕阅读器用户感到困惑,因为他们可能会听到列表序号,但在视觉上看不到它们。建议仅在确实需要完全控制列表序号的视觉呈现,并且经过充分测试后才使用这种方法。

  4. 使用 JavaScript 增强可访问性 (复杂情况下):

    在某些情况下,可能需要使用 JavaScript 来增强列表序号的可访问性。例如,可以使用 JavaScript 来动态地为列表项添加 ARIA 属性,或者修改屏幕阅读器读取的内容。

    示例:

    <!DOCTYPE html>
    <html>
    <head>
    <title>JavaScript 计数器示例</title>
    <style>
    ol {
      counter-reset: item;
      list-style-type: none;
    }
    
    li {
      counter-increment: item;
    }
    
    li::before {
      content: counter(item) ". ";
      font-weight: bold;
    }
    </style>
    </head>
    <body>
    
    <ol id="myList">
      <li>First item</li>
      <li>Second item</li>
      <li>Third item</li>
    </ol>
    
    <script>
    const list = document.getElementById('myList');
    const listItems = list.querySelectorAll('li');
    
    listItems.forEach((item, index) => {
      item.setAttribute('aria-label', `Item ${index + 1} of list`);
    });
    </script>
    
    </body>
    </html>

    在这个例子中,我们使用 JavaScript 为每个列表项添加了 aria-label 属性,提供了更详细的上下文信息。

  5. 测试和验证:

    最重要的是,要使用屏幕阅读器测试和验证你的代码,以确保列表序号对屏幕阅读器用户来说是可访问的。可以使用各种屏幕阅读器,例如 NVDA、JAWS 和 VoiceOver,来测试你的代码。

不同情况下的最佳实践总结

情况 最佳实践
使用默认列表序号,不需要自定义样式 使用 olul 元素,并使用默认的列表样式。
需要自定义列表序号的样式,但不需要复杂的交互 使用 counter() 函数生成序号,并使用 aria-label 属性为列表提供上下文。或者使用::marker伪元素。
需要复杂的列表交互或动态更新序号 使用 JavaScript 来增强可访问性,例如,动态地为列表项添加 ARIA 属性,或者修改屏幕阅读器读取的内容。
需要隐藏视觉上的序号,但保留语义上的列表结构 谨慎使用隐藏视觉上的序号的方法,并确保经过充分测试。考虑使用 JavaScript 来动态地添加 ARIA 属性,以提供额外的上下文信息。 (强烈建议避免)

一个完整的例子,综合使用 ARIA 和 JavaScript

<!DOCTYPE html>
<html>
<head>
<title>完整的可访问列表计数器示例</title>
<style>
ol {
  counter-reset: item;
  list-style-type: none;
  padding-left: 0; /* 移除默认的左侧内边距 */
}

li {
  counter-increment: item;
  margin-bottom: 0.5em;
}

li::before {
  content: counter(item) ". ";
  font-weight: bold;
  display: inline-block; /* 确保序号占据空间 */
  width: 2em; /* 固定宽度,保持对齐 */
  text-align: right; /* 右对齐序号 */
}
</style>
</head>
<body>

<ol id="accessibleList" aria-label="A list of important steps">
  <li>Prepare the ingredients.</li>
  <li>Mix the ingredients together.</li>
  <li>Bake in the oven for 30 minutes.</li>
  <li>Let it cool down before serving.</li>
</ol>

<script>
  const list = document.getElementById('accessibleList');
  const listItems = list.querySelectorAll('li');

  listItems.forEach((item, index) => {
    item.setAttribute('aria-posinset', index + 1);
    item.setAttribute('aria-setsize', listItems.length);
  });
</script>

</body>
</html>

解释:

  • aria-label: 为 ol 元素添加了 aria-label 属性,提供了列表的上下文信息。
  • aria-posinsetaria-setsize: 使用 JavaScript 为每个 li 元素添加了 aria-posinsetaria-setsize 属性。aria-posinset 表示列表项在列表中的位置,aria-setsize 表示列表的总大小。这些属性可以帮助屏幕阅读器用户更好地理解列表的结构。
  • CSS 样式: CSS 样式用于控制列表序号的视觉呈现,例如,设置字体粗细、对齐方式和宽度。

结论

CSS 计数器是一种强大的工具,可以用于自动编号列表和其他需要按顺序排列的内容。然而,在使用 counter() 函数生成列表序号时,需要特别注意可访问性问题。通过使用 ARIA 属性、JavaScript 和适当的测试,可以确保列表序号对屏幕阅读器用户来说是可访问的。

记住,可访问性不仅仅是一种技术要求,更是一种道德责任。作为开发者,我们应该努力创建对所有人来说都易于访问和使用的网站和应用程序。

一些想法

CSS 计数器生成列表序号时,务必关注屏幕阅读器的体验,通过 ARIA 属性等方式提供足够的上下文信息。测试和验证是不可或缺的步骤,确保用户能够理解列表的结构和内容。

更多IT精英技术系列讲座,到智猿学院

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注