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-hidden
div 添加一些属性来指示折叠面板内容的状态。如果折叠元素处于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-hidden
和aria-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 美元或以上,就可以为我以后的博客文章投票!干杯!祝你度过愉快的一周!