跟着 MDN 学无障碍 Day 7:WAI-ARIA 基础
引言随着 Web 应用日益复杂化和动态化开发者面临的不仅是功能实现上的挑战还有日益严峻的无障碍问题。传统的 HTML 语义元素在处理静态内容时表现出色但当涉及复杂的 UI 控件、动态内容更新以及非标准交互时其表达能力就显得捉襟见肘。WAI-ARIA 正是为解决这些问题而诞生的技术规范它通过一组额外的 HTML 属性来弥补语义的缺失让辅助技术能够正确理解并传达页面内容。什么是 WAI-ARIA背景与诞生在 HTML5 提供nav、footer、aside等语义标签之前开发者通常使用带有id或class的div来构建页面结构。例如主导航通常写作div classnav。这种做法对于视力正常的用户来说没有问题但对于依赖屏幕阅读器的用户而言这些div无法传达任何结构信息用户无法快速定位到主导航或侧边栏等重要区域。早期的解决办法是在页面顶部放置隐藏的跳转链接例如ahref#hiddenclasshiddenSkip to navigation/a这种方法虽然在一定程度上提供了帮助但使用场景有限且在不同辅助技术中的表现并不一致。另一个典型的场景涉及复杂的表单控件如日期选择器或范围滑块。HTML5 提供了对应的input类型但跨浏览器兼容性差且样式难以定制因此开发者往往借助 JavaScript 库来实现这些功能。这些库通常使用大量嵌套的div构建界面视觉上功能完整但屏幕阅读器只能看到一堆结构混乱且毫无语义的元素。WAI-ARIA 的核心概念WAI-ARIA 是 W3C 制定的一套规范定义了可以在其他元素上使用的 HTML 属性用以补充缺失的语义信息。规范主要包含三大类特性角色用于定义元素的功能。许多标志性角色与 HTML5 结构元素的语义相对应例如rolenavigation对应nav元素rolecomplementary对应aside元素。此外还有描述更具体 UI 结构的角色如roletablist、roletab、rolesearch等。属性为元素赋予额外的含义或语义信息。例如aria-requiredtrue表示表单字段必须填写aria-labelledby允许引用页面中的其他元素作为标签这弥补了label元素只能关联单个输入框的局限。状态用于表达元素当前的条件与属性不同的是状态会随着应用的生命周期发生变化。例如aria-disabledtrue表示表单输入处于禁用状态开发者可以通过 JavaScript 动态更新这一状态。WAI-ARIA 的重要特征在于它不会对页面的视觉呈现和 DOM 结构产生任何影响。它的作用仅限于通过浏览器向无障碍 API 暴露更多信息这些信息最终被屏幕阅读器等辅助技术所利用。开发者也可以利用这些 ARIA 属性作为 CSS 选择器进行样式定制。何时使用 WAI-ARIA使用 WAI-ARIA 应遵循一个核心原则仅在必要时使用。理想情况下开发者应当优先使用原生 HTML 元素来实现语义表达因为原生元素自带键盘交互和无障碍支持无需额外工作。但现实情况下存在两种必须借助 ARIA 的场景一是对代码的控制权有限无法修改底层 HTML 结构二是需要实现原生 HTML 无法支持的复杂功能。WAI-ARIA 主要解决了四个核心领域的问题路标与地标ARIA 的角色属性可以为页面不同功能区域提供语义标识帮助屏幕阅读器用户快速导航。动态内容更新aria-live属性可以通知屏幕阅读器关注特定区域的内容变化。键盘无障碍操作通过tabindex属性的扩展值开发者可以将原本无法聚焦的元素纳入键盘导航序列。非语义控件当开发者必须使用div等非语义元素构建复杂 UI 时ARIA 可以为其补充缺失的语义信息。路标与地标的实现基础结构分析以一份典型的页面结构为例包含页眉、导航、主内容、侧边栏和页脚。使用 HTML5 语义标签编写如下headerh1页面标题/h1navulliahref#首页/a/liliahref#关于/a/li/ulforminputtypetextplaceholder搜索//form/nav/headermainarticle文章内容/articleaside侧边栏信息/aside/mainfooter页脚信息/footer在支持 HTML5 的浏览器中屏幕阅读器能够识别这些语义元素并告知用户。例如 VoiceOver 会报告 banner、navigation、main、complementary 和 footer 等地标。使用 ARIA 增强地标尽管 HTML5 语义标签已能提供基本的地标信息但仍有优化空间。搜索表单是用户经常需要快速定位的功能可以为表单添加rolesearch来明确其用途headerh1页面标题/h1navrolenavigationulliahref#首页/a/liliahref#关于/a/li/ulformrolesearchinputtypesearchnameqplaceholder搜索关键词aria-label搜索整个站点内容//form/nav/headermainarticlerolearticle文章内容/articleasiderolecomplementary侧边栏信息/aside/mainfooter页脚信息/footer在这个优化版本中search角色使搜索表单在地标菜单中成为独立项目便于快速访问。同时input元素上的aria-label属性为没有可见标签的输入框提供了描述性名称屏幕阅读器会将其朗读给用户。对于仍在使用 IE8 等老旧浏览器的用户这些 ARIA 角色的价值更为突出。如果页面完全使用div构建ARIA 角色几乎是提供必要语义的唯一手段。动态内容更新的通知实时区域的问题现代 Web 应用中频繁出现动态内容更新例如实时聊天、股票行情、购物车状态变化等。这些内容变化虽然视觉上可见但默认情况下屏幕阅读器无法感知。用户可能完全不知道页面中的某个区域已经更新了内容。以下是一个简单的随机名言展示示例页面每隔十秒更新一次引用的内容sectionh1随机名言/h1blockquotep/p/blockquote/sectionJavaScript 代码通过fetch加载数据并使用setInterval定时更新内容constintervalIDsetInterval(showQuote,10000);功能本身没有问题但屏幕阅读器用户无法获知内容正在发生变化。aria-live 的解决方案aria-live属性可以将元素标记为实时区域当内容发生变化时屏幕阅读器会朗读更新内容。该属性有三个可选值off默认值不进行任何通知。polite在用户空闲时进行通知不会打断当前操作。assertive尽快通知用户可能会轻微打断当前操作。rude立即通知可能严重打断用户操作应谨慎使用。对于大多数场景assertive级别的通知已经足够。将实时区域标记如下sectionaria-liveassertiveh1随机名言/h1blockquotep/p/blockquote/sectionaria-atomic 确保完整朗读默认情况下屏幕阅读器只朗读实际发生变化的部分文本。如果希望每次更新时都朗读整个内容区域可以使用aria-atomic属性sectionaria-liveassertivearia-atomictrueh1随机名言/h1blockquotep/p/blockquote/section设置为true后屏幕阅读器会将整个section视为一个不可分割的单元每次更新都会完整朗读标题和内容。键盘无障碍的优化tabindex 的扩展用法HTML 中可聚焦的元素默认包括链接、按钮和表单控件。开发者可以通过键盘的 Tab 键在这些元素之间顺序导航。但对于使用div或span构建的自定义控件默认是不可聚焦的键盘用户无法访问。WAI-ARIA 扩展了tabindex属性的用法提供了两个关键值tabindex0使元素成为可 Tab 聚焦的它会按照 DOM 顺序出现在 Tab 键的导航序列中。这是最常用的值。tabindex-1允许元素通过 JavaScript 编程方式获得焦点例如在点击事件中调用focus方法但元素本身不会出现在 Tab 键的导航序列中。实践示例将一个div改造为可交互的按钮时需要同时添加tabindex和键盘事件处理divdata-message第一条消息tabindex0rolebutton点击我/div对应的 JavaScript 需要处理键盘事件确保按下空格键或回车键时触发与鼠标点击相同的操作constbuttondocument.querySelector(div[rolebutton]);button.addEventListener(click,handleClick);button.addEventListener(keydown,function(e){if(e.keyEnter||e.key ){e.preventDefault();handleClick.call(this,e);}});这种做法使得自定义控件既可通过鼠标操作也可通过键盘操作实现了设备无关的交互。非语义控件的语义补充表单验证中的 ARIA表单验证是无障碍实践中的重要环节。在错误信息的展示中可以使用rolealert将错误容器标记为告警区域divclasserrorsrolealertaria-relevantallul/ul/divrolealert自动将元素转换为实时区域当错误信息出现时屏幕阅读器会立即朗读。aria-relevantall则指示屏幕阅读器在错误列表发生任何变化时都进行通知包括添加和删除。对于必填字段可以使用aria-required属性进行标记inputtypetextnamenameidnamearia-requiredtrue/inputtypenumbernameageidagearia-requiredtrueplaceholder请输入 1 到 150 之间的数字/屏幕阅读器在聚焦这些输入框时会朗读 required 提示让用户了解该字段必须填写。描述非语义按钮当开发者不得不使用div构建按钮时仅添加tabindex无法解决语义问题。屏幕阅读器仍然无法识别这个元素是一个按钮divdata-message这是第一个按钮tabindex0点击我/div屏幕阅读器可能会将其朗读为group或其他不明确的角色。添加rolebutton可以解决这一问题divdata-message这是第一个按钮tabindex0rolebutton点击我/div此时屏幕阅读器会正确地将该元素识别为button并向用户传达其功能。即便如此开发者仍应优先使用原生的button元素因为它提供了所有内置的无障碍支持包括键盘交互、焦点管理和语义信息。复杂小部件的 ARIA 实现选项卡界面的 ARIA 改造以选项卡界面为例原始实现使用链接列表作为选项卡触发器内容面板则是普通div。这种结构在功能上可以工作但屏幕阅读器无法理解选项卡之间的关系。ARIA 优化的版本使用了以下结构和属性ulroletablistliclassactiveroletabaria-selectedtruearia-setsize3aria-posinset1tabindex0标签一/liliroletabaria-selectedfalsearia-setsize3aria-posinset2tabindex0标签二/liliroletabaria-selectedfalsearia-setsize3aria-posinset3tabindex0标签三/li/uldivclasspanelsarticleclassactive-panelroletabpanelaria-hiddenfalse标签一的内容/articlearticleroletabpanelaria-hiddentrue标签二的内容/articlearticleroletabpanelaria-hiddentrue标签三的内容/article/divroletablist标记了选项卡容器的角色。roletab和roletabpanel分别定义了选项卡和内容面板。aria-selected指示当前选中的选项卡。aria-hidden控制内容面板是否对辅助技术可见。aria-setsize和aria-posinset共同告知屏幕阅读器当前元素在系列中的位置例如 1 of 3 或 2 of 3。这些属性使得屏幕阅读器能够正确识别选项卡结构并告知用户当前所在的选项卡编号和总数。状态管理的最佳实践在选项卡切换时需要同步更新多个 ARIA 状态。被选中的选项卡应设置aria-selectedtrue对应的内容面板应设置aria-hiddenfalse。未选中的选项卡和面板则应设置为相反的值。JavaScript 负责在用户切换时更新这些状态属性同时更新对应的 CSS 类来控制视觉样式。这种做法确保了视觉呈现和无障碍语义始终保持一致。总结WAI-ARIA 为 Web 无障碍提供了一套强大且灵活的语义补充机制。通过角色、属性和状态三大核心概念它弥合了复杂 UI 与辅助技术之间的沟通鸿沟。在路标与地标方面ARIA 角色帮助屏幕阅读器用户快速定位页面中的关键功能区域。在动态内容方面aria-live确保用户能够感知到内容更新。在键盘操作方面tabindex扩展值让自定义控件进入键盘导航序列。在非语义控件方面role和aria-*属性为div构建的界面补充了缺失的语义。使用 WAI-ARIA 应遵循仅在必要时使用的原则。优先使用原生 HTML 元素仅在无法实现或控制权受限时才借助 ARIA。同时建议让真实的用户参与测试包括使用屏幕阅读器和键盘导航的用户他们的反馈往往能发现开发者难以察觉的问题。