无障碍功能如何让我更好地掌握 JavaScript - 第二部分

2025-06-09

无障碍功能如何让我更好地掌握 JavaScript - 第二部分

最初发布于www.a11ywithlindsey.com

内容警告:本帖中有 gif。

嘿,朋友们!今天的文章是《无障碍功能如何教会我更好地使用 JavaScript》的后续。如果你读过我的文章,你会发现我最喜欢的话题之一就是 JavaScript 和无障碍功能。我会讲解 JavaScript 对于实现交互元素无障碍的重要性。

在我之前的文章中,我谈到了如何创建一个兼顾可访问性的弹出式语言菜单。实现功能性和可访问性是我第一次接触原生 JavaScript。代码确实需要改进,我们在文章中也讨论过这个问题。然而,实现菜单的可访问性开始帮助我更好地理解 JavaScript。

今天,我们将讨论我是如何将一些令人尴尬的“手风琴”标记变得易于理解的。记住,一个基本要求是,我不能以任何方式修改内容标记。这个页面是一篇 WordPress 文章,这意味着我无法进入并编辑文章,使其变成我想要的标记。

开始

所以,这就是起始标记。

我喜欢简洁的 HTML,但无法修改标记让我很恼火。这个标记简直乱七八糟。首先,它以一个无序列表开始,虽然不算最糟糕,但也算不上理想。然后在列表项中,它包含一个用于面板标题的 span,一个 h3,另一个无序列表元素,最后是一个单独的列表项(也就是说它根本就不是一个列表?)。

我非常讨厌这种标记。

现在我已经讲完了,让我们来谈谈这里的几个目标:

  • 加载页面时隐藏面板
  • 单击即可打开或关闭手风琴面板。
  • 使用空格键或回车键可以打开和关闭手风琴面板。
  • 使该跨度可聚焦

我添加了一点 SCSS 来清理标记。我还在 CodePen 设置中添加了 normalize.css。

现在让我们来谈谈我四年前是如何解决这个问题的。

我如何处理这个问题

声明一下,这是 Lindsey 4 年前做的。只有一件事我不会做;不过,即便如此,我还是会在这段代码中添加更多内容,我会在下一节中讲到。

首先,让我们获取一些变量:

const accordion = document.getElementById('accordion')
Enter fullscreen mode Exit fullscreen mode

然后,我们来创建一个条件语句。如果这个 accordion 存在,我们就获取一些其他变量。

if (accordion) {
  const headers = document.querySelectorAll('.accordion__header')
  const panels = document.querySelectorAll('.accordion__panel')
}
Enter fullscreen mode Exit fullscreen mode

我添加了条件语句,因为我们要循环遍历该节点列表。我不想在null

现在让我们添加事件监听器

if (accordion) {
  const headers = document.querySelectorAll('.accordion__header')
  headers.forEach(header => header.addEventListener('click', toggleAccordion))

  const panels = document.querySelectorAll('.accordion__panel')
}
Enter fullscreen mode Exit fullscreen mode

然后,让我们添加这个函数,其中.accordion__header代表this.nextElementSibling.accordion__panel

function toggleAccordion() {
  this.nextElementSibling.classList.toggle('visually-hidden')
}
Enter fullscreen mode Exit fullscreen mode

如果我们转到元素检查器并单击手风琴项目,我们会注意到类切换。

单击“问题 1”并查看 accordion__panel 类有一个被切换的视觉隐藏类。

然后让我们visually-hidden在 SCSS 中添加类(来源:A11y 项目):

.visually-hidden {
  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 */
}
Enter fullscreen mode Exit fullscreen mode

现在让我们将visually-hidden类添加到面板中,以便进行视觉切换。

if (accordion) {
  const headers = document.querySelectorAll('.accordion__header')
  headers.forEach(header => header.addEventListener('click', toggleAccordion))

  const panels = document.querySelectorAll('.accordion__panel')
  panels.forEach(panel => panel.classList.add('visually-hidden'))
}
Enter fullscreen mode Exit fullscreen mode

如果您不考虑可访问性,您可能只添加一个点击事件就完事了。由于这些不是按钮,所以我们必须添加按键事件。我们需要复制按钮的功能。这就是为什么使用语义化 HTML 是提高可访问性的最佳方法。

首先,我们必须为每个标题添加一个 tabindex 0。

if (accordion) {
  const headers = document.querySelectorAll('.accordion__header')
  headers.forEach(header => {
    header.tabIndex = 0
    header.addEventListener('click', toggleAccordion)
  })

  const panels = document.querySelectorAll('.accordion__panel')
  panels.forEach(panel => panel.classList.add('visually-hidden'))
}
Enter fullscreen mode Exit fullscreen mode

当我们这样做时,只要按下键,我们就可以看到焦点样式tab

通过手风琴按钮可以看到默认浏览器设置中的清晰焦点样式。

如果我们按下回车键或空格键,什么也不会发生。这是因为这个button元素没有内置点击键盘事件。这就是为什么我时不时地会强调使用语义化 HTML。

我们必须keypress在标题元素上添加一个事件。

headers.forEach(header => {
  header.tabIndex = 0
  header.addEventListener('click', toggleAccordion)
  header.addEventListener('keypress', toggleAccordion)
})
Enter fullscreen mode Exit fullscreen mode

k这“起作用了”,但并不完全符合我们的预期。因为我们没有区分要切换哪个键,所以按下哪个键或空格键都无所谓。

首先,让我们将事件传递给toggleAccordion函数,console.log()然后

function toggleAccordion(e) {
  console.log(e)
  this.nextElementSibling.classList.toggle('visually-hidden')
}
Enter fullscreen mode Exit fullscreen mode

插播一下。虽然我更喜欢用按钮来实现,但学习这种错误的方式让我学到了很多 JavaScript 知识。我学到了事件处理程序和事件对象。作为一个 JavaScript 新手,我从探索中学到了很多,即使这不是编写代码的最佳方式。

回到事件的话题。当我们在控制台中打开它时,我们会看到该事件的一系列属性。

开发者工具中的 JavaScript 控制台显示了 KeyPress 的事件属性。我们发现有两个属性格外醒目:“key” 的值为“j”,而“code” 的值为“KeyJ”。

我看到了一些可以使用的东西,特别是codeor key。我将使用key属性,因为当我按下空格键时,它会更加冗长。

所以我可以这样做,对吗?

function toggleAccordion(e) {
  if (e.code === 'Enter' || e.code === 'Space') {
    this.nextElementSibling.classList.toggle('visually-hidden')
  }
}
Enter fullscreen mode Exit fullscreen mode

嗯,不行。因为这没有考虑到click事件本身。点击事件没有这个code属性。它们有哪些属性可以用来处理这个点击事件呢?让我们把这个console.log(e)属性添加到函数中,看看有什么可用的。

开发者工具中的 JavaScript 控制台显示了 Click 事件的属性。我们看到“type”属性的值为“click”。

所以现在,我检查是否type是点击或者code是一个空格或输入。

为了使其更易于阅读,我将把 拆分code成一个返回 true 或 false 的三元运算符。我最初做这件事时并没有这样做,但我想在条件语句中增加一点可读性。

function toggleAccordion(e) {
  const pressButtonKeyCode =
    e.code === 'Enter' || e.code === 'Space' ? true : false

  if (e.type === 'click' || pressButtonKeyCode) {
    this.nextElementSibling.classList.toggle('visually-hidden')
  }
}
Enter fullscreen mode Exit fullscreen mode

现在我们可以单击并用空格键和回车键打开。

点击按钮,相应的折叠面板部分就会打开或关闭。然后我们切换到该部分,用键盘打开或关闭它。

我还有很多地方需要改进,我们接下来会逐一讲解。如果你想查看代码,可以看看下面的 CodePen:

我现在想改变什么

虽然从技术上来说可行,但并非最理想的方案。我学习 JavaScript 的时候根本不知道什么是渐进增强,也不知道什么是 ARIA。

那么,让我们开始一步步来吧。如果你读过第一部分,你就会知道我非常喜欢用一个no-js类来检测 JavaScript 是否已加载。

<ul id="accordion" class="accordion no-js">
  <!-- Children elements -->
</ul>
Enter fullscreen mode Exit fullscreen mode

然后,当我们的 JavaScript 加载时,我们做的第一件事就是删除该类。

const accordion = document.getElementById('accordion')
accordion.classList.remove('no-js')
Enter fullscreen mode Exit fullscreen mode

如果该类存在,我们将添加一些默认样式no-js,这意味着 JavaScript 不会加载:

.accordion {
  &.no-js {
    .accordion__header {
      display: none;
    }

    .accordion__item {
      border-top: 0;
      border-bottom: 0;

      &:first-child {
        border-top: 1px solid;
      }

      &:last-child {
        border-bottom: 1px solid;
      }
    }

    .accordion__panel {
      display: block;
      border-top: 0;
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

我已经删除了从技术上来说不是按钮的按钮,并且默认打开所有内容。

使用 no-js 类在浏览器中渲染的 HTML。没有其他按钮,只有标题和正文,并且两者都没有被隐藏

现在,回到 JavaScript。在标题上,我们要将aria-expanded属性设置为 false,并赋予其按钮角色。

headers.forEach(header => {
  header.tabIndex = 0
  header.setAttribute('role', 'button')
  header.setAttribute('aria-expanded', false)
  header.addEventListener('click', toggleAccordion)
  header.addEventListener('keypress', toggleAccordion)
})
Enter fullscreen mode Exit fullscreen mode

当我们设置角色时,我将把面板的角色设置为region

if (accordion) {
  // header code
  panels.forEach(panel => {
    panel.setAttribute('role', 'region')
  }
}
Enter fullscreen mode Exit fullscreen mode

接下来,我将切换 aria-expanded 并删除函数中类的切换。需要注意的是,即使我们将属性设置为布尔值,它getAttribute()返回的仍然是字符串。

function toggleAccordion(e) {
  const pressButtonKeyCode =
    e.code === 'Enter' || e.code === 'Space' ? true : false

  const ariaExpanded = this.getAttribute('aria-expanded')

  if (e.type === 'click' || pressButtonKeyCode) {
    if (ariaExpanded === 'false') {
      this.setAttribute('aria-expanded', true)
    } else {
      this.setAttribute('aria-expanded', false)
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

我们不需要在视觉上隐藏内容,因为我们有一个控制信息的按钮。阅读不想要的信息对屏幕阅读器用户来说不是一个好的体验。我喜欢在 CSS 中使用和来aria-expanded切换面板。display: nonedisplay: block

.accordion {
  &__header {
    // more scss
    &[aria-expanded='true'] + .accordion__panel {
      display: block;
    }
  }

  &__panel {
    display: none;
    padding: 1rem;
    border-top: 1px solid;

    h3 {
      margin-top: 0;
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

我将添加一些 ARIA 属性来帮助将标题和面板关联在一起。

我以WAI-ARIA 创作实践为基础。

首先,标题:

headers.forEach(header => {
  header.tabIndex = 0
  header.setAttribute('role', 'button')
  // This will match the aria-labelledby on the panel
  header.setAttribute('id', `accordion-header-${i + 1}`)
  header.setAttribute('aria-expanded', false)
  // This will match the id on the panel
  header.setAttribute('aria-controls', `accordion-section-${i + 1}`)
  header.addEventListener('click', toggleAccordion)
  header.addEventListener('keypress', toggleAccordion)
})
Enter fullscreen mode Exit fullscreen mode

然后我们会把它们拿来,确保它们与面板准确匹配

panels.forEach(panel => {
  // This will match the aria-controls on the header
  panel.setAttribute('id', `accordion-section-${i+1}`)
  panel.setAttribute('role', 'region')
  // This will match the id on the header
  panel.setAttribute('aria-labelledby', `accordion-header-${i+1}`)
}
Enter fullscreen mode Exit fullscreen mode

如果您想试用该代码,请分叉 CodePen 并进行检查。

结论

这是迄今为止最理想的标记吗?不是。这是否教会了我很多 JavaScript 知识?是的。这是否教会了我使用内置键盘事件的按钮的价值?是的。

保持联系!如果你喜欢这篇文章:

  • 在Twitter上告诉我,并和你的朋友们分享这篇文章!另外,如果你有任何后续问题或想法,也欢迎随时在 Twitter 上给我留言。
  • 在Patreon上支持我!如果您喜欢我的作品,可以考虑每月捐赠 1 美元。如果您捐赠 5 美元或更高,您将可以对以后的博客文章进行投票!我每月还会为所有赞助人举办“问我任何问题”活动!
  • 参加为期 10 天的 a11y 挑战,获得更多无障碍乐趣!

干杯!祝你度过愉快的一周!

鏂囩珷鏉yu簮锛�https://dev.to/lkopacz/how-accessibility-taught-me-to-be-better-at-javascript-part-two-20af
PREV
和我一起了解网络阅读障碍!
NEXT
探索无障碍 CLI 工具