JavaScript 和可访问性:手风琴

2025-06-07

JavaScript 和可访问性:手风琴

最初发布于www.a11ywithlindsey.com

我第一次写关于JavaScript 和可访问性的文章时,就承诺要把它做成一个系列。现在我决定用我的Patreon来投票选出我的下一篇博文。这个主题赢了,我终于有更多时间写 JavaScript 了!

所以这个话题,我将深入探讨如何让手风琴面板更容易上手!我们的重点是:

  • 使用键盘访问手风琴
  • 屏幕阅读器支持

HTML结构

我做了一些关于 HTML 结构的研究。我阅读了a11y 项目提供的Scott O'Hara 手风琴代码链接。我还阅读了Don 对 aria-controls 的看法——简而言之,他认为它们很垃圾。我忍不住阅读了WAI-ARIA 手风琴示例,因为它们制定了很多标准。我希望通过提供所有关于理想情况的信息,能够帮助解释为什么这里的所有细节都很重要。很容易不知所措,而我在这里就是为了帮助你!

因此,如果您阅读了我的帖子《提高键盘可访问性的 3 个简单技巧》,您可能会记得我对语义 HTML 的热爱。

如果您需要 JavaScript 来实现可访问性,语义 HTML 可以让您的工作变得容易得多。

我发现很多示例都使用语义按钮元素作为折叠标题。这些示例还使用了 div 标签作为兄弟元素。以下是我的代码开头:

添加 ARIA 属性

我在之前的一篇文章中提到过,ARIA 并不能取代语义 HTML 。新推出的 HTML 功能一直在取代 ARIA。理想情况下,我会使用详细信息元素。遗憾的是,根据浏览器兼容性部分,它不支持 Edge 和 IE11。在浏览器支持改进之前,我将继续使用“老式”的方法。我会根据我们的需求添加 ARIA。我期待看到兼容性扩展到 Edge!

首先,我要给aria-hiddendiv 添加一些属性来指示折叠面板内容的状态。如果折叠元素处于closed 状态,我们希望屏幕阅读器无法识别该内容。你能想象,阅读那些你不感兴趣的内容是多么令人厌烦吗?

- <div id="accordion-section-1">
+ <div id="accordion-section-1" aria-hidden="true">
...
...
- <div id="accordion-section-2">
+ <div id="accordion-section-2" aria-hidden="true">
...
...
- <div id="accordion-section-3">
+ <div id="accordion-section-3" aria-hidden="true">

接下来我们要确保aria-expanded按钮有一个属性。当我们点击按钮时,它会告诉我们某个元素是展开还是折叠。

- <button id="accordion-open-1">
+ <button id="accordion-open-1" aria-expanded="false">
...
...
- <button id="accordion-open-2">
+ <button id="accordion-open-2" aria-expanded="false">
...
...
- <button id="accordion-open-3">
+ <button id="accordion-open-3" aria-expanded="false">

对我来说,ARIA 的原则是“少即是多”。我打算就此打住,并在后续章节中使用 JavaScript 来切换 ARIA 属性的状态。

添加一些样式

我不会过多地关注 CSS 的细节。如果你需要 CSS 资源,Ali Spittel 的文章《CSS:从零到大师》和 Emma Wedekind 的文章《CSS 特殊性》都很不错。

首先,我为 div 和按钮添加了类,以便更好地衡量。

- <button id="accordion-open-1" aria-expanded="false">
+ <button id="accordion-open-1" class="accordion__button" aria-expanded="false">
    Section 1
  </button>
- <div id="accordion-section-1" aria-hidden="true">
+ <div id="accordion-section-1" class="accordion__section" aria-hidden="true">

然后我给按钮添加了一些样式。这是我用 SCSS 编写的 CodePen。

(快速提示:对于 iframe 上的三角形,我使用了CSS 技巧中的CSS 三角形文章。)

我想明确指出这段代码:

.accordion {
  // previous styling
  &__button.expanded {
    background: $purple;
    color: $lavendar;
  }
}

我想指定按钮打开时的外观。我喜欢它吸引你的眼球和注意力到打开部分的方式。现在我已经了解了它们的大致外观,我将添加折叠样式。此外,我还添加了一些打开样式。

  &__section {
    border-left: 1px solid $purple;
    border-right: 1px solid $purple;
    padding: 1rem;
    background: $lavendar;
+   max-height: 0vh;
+   overflow: hidden;
+   padding: 0;
  }

+ &__section.open {
+   max-height: 100vh;
+   overflow: auto;
+   padding: 1.25em;
+   visibility: visible;
+ }

最后,让我们为按钮添加一些焦点和悬停样式:

  $purple: #6505cc;
+ $dark-purple: #310363;
  $lavendar: #eedbff;
  &__button {
    position: relative;
    display: block;
    padding: 0.5rem 1rem;
    width: 100%;
    text-align: left;
    border: none;
    color: $purple;
    font-size: 1rem;
    background: $lavendar;

+   &:focus,
+   &:hover {
+     background: $dark-purple;
+     color: $lavendar;
+
+     &::after {
+       border-top-color: $lavendar;
+     }
+   }

简短说明:您可以通过添加.accordion__button[aria-expanded="true"]或 来添加样式.accordion__section[aria-hidden="false"]。但是,我个人更喜欢使用类来设置样式,而不是使用属性。因人而异!

JavaScript 切换

现在让我们开始以可访问的方式切换手风琴折叠面板的有趣部分。首先,我要抓取所有.section__button元素。

const accordionButtons = document.querySelectorAll('.accordion__button')

然后我想逐步遍历 JavaScript 返回的 HTML 集合中的每个元素。

accordionButtons.forEach(button => console.log(button))
// returns <button id="accordion-open-1" class="accordion__button" aria-expanded="false">
//    Section 1
//  </button>
//  <button id="accordion-open-2" class="accordion__button" aria-expanded="false">
//    Section 2
//  </button>
//  <button id="accordion-open-3" class="accordion__button" aria-expanded="false">
//    Section 3
//  </button>

然后,为了达到视觉效果,我想为每个项目切换打开和关闭的类名。如果你还记得我们之前添加的.open.expanded类名,这里就是我们切换它们的地方。我将使用匹配的 ID 中的数字来获取该按钮对应的部分。

accordionButtons.forEach(button => {
  // This gets the number for the class.
  // e.g. id="accordion-open-1" would be "1"
  const number = button
    .getAttribute('id')
    .split('-')
    .pop()

  // This gets the matching ID. e.g. the
  // section id="accordion-section-1" that is underneath the button
  const associatedSection = document.getElementById(
    `accordion-section-${number}`
  )
})

现在我们在回调函数和相关部分中获得了当前值button。接下来就可以切换类了!

button.addEventListener('click', () => {
  button.classList.toggle('expanded')
  associatedSection.classList.toggle('open')
})

切换类并非我们想要的全部。我们还想切换 aria 属性。上一节提到,aria 属性会将状态传递给屏幕阅读器。更改类会向视觉用户(而非屏幕阅读器)显示发生了什么。接下来,我检查按钮是否包含其中一个元素中的类。如果包含,我将状态切换为aria-hiddenaria-expanded

button.addEventListener('click', () => {
  button.classList.toggle('expanded')
  associatedSection.classList.toggle('open')

+ if (button.classList.contains('expanded')) {
+   console.log('open?')
+ }
})

设置好类别后,条件就会触发,如果类别已经展开,则表示它已打开!所以,这就是我们想要使用状态并传达它已打开状态的地方。

button.addEventListener('click', () => {
  button.classList.toggle('expanded')
  associatedSection.classList.toggle('open')

  if (button.classList.contains('expanded')) {
    button.setAttribute('aria-expanded', true)
    associatedSection.setAttribute('aria-hidden', false)
  } else {
    button.setAttribute('aria-expanded', false)
    associatedSection.setAttribute('aria-hidden', true)
  }
})

现在我们可以使用空格键或回车键打开和关闭手风琴!

当我浏览折叠面板标题而不打开它们时,它们不会读取该部分的内容。这真是太好了!当我打开它时,我就能阅读了。

尼尔·帕特里克·哈里斯坐在汽车驾驶座上,咧嘴一笑。然后他点点头,竖起大拇指。

渐进增强

现在,我知道我们多么依赖 JavaScript 加载,尤其是在我们使用的所有框架中。既然我们了解了功能,那就让我们稍微重构一下代码。目标是确保即使 JavaScript 未启用或用户遇到连接问题,任何人都可以访问手风琴折叠面板。

我的最后一招是

  • 默认保持所有手风琴部分打开(.open向 HTML 部分添加一个类)
  • JavaScript 加载后删除“open”类。
  • 使用 JavaScript 添加所有 aria 属性,然后从 HTML 中删除它们

我想分别从按钮和部分中删除aria-expanded="false"aria-hidden="true"。我还想将open类添加到 html 中,以便默认打开。

- <button id="accordion-open-1" class="accordion__button" aria-expanded="false">
+ <button id="accordion-open-1" class="accordion__button">
    Section 1
  </button>
- <div id="accordion-section-1" class="accordion__section" aria-hidden="true">
+ <div id="accordion-section-1" class="accordion__section open">

我想在 forEach 循环中设置这些属性并删除该类accordionButtons

accordionButtons.forEach(button => {
+ button.setAttribute('aria-expanded', false);
  const expanded = button.getAttribute('aria-expanded');

然后我想创建一个accordionsSections变量并做两件事:

  • 设置aria-hidden属性
  • 删除.open该类。
const accordionSections = document.querySelectorAll('.accordion__section');

accordionSections.forEach(section =>  {
  section.setAttribute('aria-hidden', true)
  section.classList.remove('open')
})

大功告成!记住,我们并没有删除任何其他代码或事件监听器。我们只是用 JavaScript 添加了所有这些属性。

结论

你觉得这篇文章怎么样?它对你有帮助吗?你对这个<details>元素感到兴奋吗?请在推特上告诉我你的想法!另外,我现在有了一个Patreon 账号!如果你喜欢我的作品,可以考虑成为我的 Patreon。如果你承诺捐赠 5 美元或以上,就可以为我以后的博客文章投票!干杯!祝你度过愉快的一周!

文章来源:https://dev.to/lkopacz/javascript-and-accessibility-accordions-5ecd
PREV
🔥 如何在 2024 年学习 RAG:从初学者到专家(循序渐进)🚀
NEXT
创建自定义键盘可访问复选框