第10课:可访问性(a11y)基础——ARIA、键盘导航与屏幕阅读器
可访问性(Accessibility,常缩写为 a11y,因首尾字母间有 11 个字母)是指让网站或应用对所有用户——包括残障人士——可感知、可操作、可理解、稳健(WCAG 四大原则)。这不仅是道德和法律(如 ADA、Section 508)的要求,更直接扩大用户群体、提升 SEO、改善整体代码质量。本节课将深入讲解可访问性的核心支柱:语义 HTML 的基础作用、ARIA 属性的正确用法、键盘导航的实现以及屏幕阅读器兼容性。
1. 可访问性的基石:语义化 HTML
在讨论 ARIA 之前,必须明确一个最高原则:好的可访问性始于正确的 HTML 语义。
1.1 原生语义的自动可访问性
浏览器和辅助技术(如屏幕阅读器)对标准 HTML 元素内置了完整的可访问性支持。例如:
<button>自动被识别为“按钮”,可通过键盘Enter或Space触发,并暴露role="button"。<a href="...">自动被识别为“链接”,可被 Tab 聚焦,屏幕阅读器会读出“链接”。<input type="checkbox">自动暴露role="checkbox"和aria-checked状态。
因此,优先使用原生语义元素是最高效、最可靠的可访问性策略。
1 | <!-- ✅ 最佳:原生按钮 --> |
1.2 标题层级(Heading Level)的可访问性意义
屏幕阅读器用户通常通过标题导航(按 H 键)来浏览页面结构。逻辑清晰、无跳跃的标题层级(<h1> → <h2> → <h3>)是用户理解页面信息架构的关键。
1 | <!-- ✅ 良好的标题结构 --> |
1.3 替代文本(Alt Text)与可感知性
所有非文本内容(图片、图标、图表)必须提供替代文本,使视障用户能通过屏幕阅读器获取信息。
1 | <!-- ✅ 描述性替代文本 --> |
2. ARIA:当 HTML 语义不够用时
ARIA(Accessible Rich Internet Applications)是一套属性,用于补充和增强 HTML 元素的可访问性语义。它不改变元素的行为或外观,仅影响辅助技术(如屏幕阅读器)对元素的解释。
核心警告:No ARIA is better than Bad ARIA. 滥用或错误使用 ARIA 比不用更糟糕。
2.1 ARIA 三大类属性
| 类别 | 作用 | 常见属性 |
|---|---|---|
| 角色(Roles) | 定义元素的类型(如按钮、对话框、标签页)。 | role="button", role="dialog", role="tablist" |
| 状态(States) | 描述元素当前的动态状态(屏幕阅读器会在状态变化时自动播报)。 | aria-expanded, aria-checked, aria-selected |
| 属性(Properties) | 提供元素的额外描述信息(如标签、描述、层级关系)。 | aria-label, aria-labelledby, aria-describedby |
2.2 最常用的 ARIA 属性详解
aria-label 与 aria-labelledby:为元素提供可访问名称
当元素没有可见文本标签,或可见文本不足以描述其功能时使用。
1 | <!-- 仅有图标没有文字的关闭按钮 --> |
aria-describedby:提供补充描述信息
关联一个或多个元素的 id,为控件提供额外的说明文字。
1 | <label for="pwd">密码:</label> |
屏幕阅读器聚焦输入框时会读出:“密码,编辑文本,密码至少 8 位,包含字母和数字。”
aria-expanded:指示可展开控件的状态
用于菜单、下拉框、手风琴等可展开/收起的控件。
1 | <button aria-expanded="false" aria-controls="menu-list">菜单</button> |
当 JavaScript 展开菜单时,需同步更新 aria-expanded="true" 并移除 hidden。
aria-hidden:对辅助技术隐藏元素
值为 true 时,该元素及其子元素对屏幕阅读器完全不可见。用于隐藏纯装饰性内容或重复内容(如重复的图标字体)。
1 | <!-- 装饰性图标,不需要被读出 --> |
aria-live:宣布动态内容更新
用于通知屏幕阅读器某区域内容已更新(如聊天消息、表单错误、股票价格)。无需移动焦点即可自动播报。
aria-live 值 |
行为 |
|---|---|
off |
默认。不宣布更新。 |
polite |
在当前播报结束后再宣布更新,不打断用户。 |
assertive |
立即打断当前播报,宣布更新内容。仅用于紧急通知(如错误)。 |
1 | <div aria-live="polite" aria-atomic="true"> |
aria-atomic="true" 表示播报该区域的完整内容,而非仅变化的部分。
2.3 ARIA 角色(Roles)使用原则
仅当原生 HTML 无法表达所需语义时,才使用 role 属性。
1 | <!-- 自定义选项卡组件(原生无 tabs 元素) --> |
常见错误:冗余的角色声明。
1 | <!-- ❌ 错误:原生 button 已有 role="button",无需重复声明 --> |
3. 键盘导航:确保所有功能可通过键盘操作
WCAG 2.1 要求:所有交互功能必须可通过键盘操作(有例外,如依赖路径的手绘)。这是运动障碍用户和重度键盘用户的基本需求。
3.1 焦点管理基础
- 可聚焦元素:
<a>(有href)、<button>、<input>、<select>、<textarea>、<details>、<summary>、以及设置了tabindex的元素。 - Tab 键:正向移动焦点。
- Shift + Tab:反向移动焦点。
3.2 tabindex 的三种用法
tabindex 值 |
行为 | 使用场景 |
|---|---|---|
0 |
元素被加入自然 Tab 顺序(按 DOM 顺序),可通过键盘聚焦。 | 使非交互元素(如自定义 <div> 按钮)可聚焦。 |
-1 |
元素从 Tab 顺序中移除,但仍可通过 JavaScript focus() 方法聚焦。 |
模态对话框内的初始焦点设置、可聚焦但不想让用户 Tab 到的元素。 |
> 0 |
强制指定 Tab 优先级。强烈不推荐,会破坏自然 DOM 顺序,导致焦点跳跃混乱。 | 几乎无正当场景,应通过调整 DOM 顺序而非修改 tabindex 来优化导航。 |
3.3 键盘陷阱与模态对话框
当打开模态对话框时,焦点应被限制在对话框内部(按 Tab 不会跑到背景页面),直到用户关闭对话框。
1 | // 简化的焦点陷阱逻辑 |
3.4 跳过导航链接(Skip Link)
为键盘用户提供“跳转到主要内容”的链接,避免每次页面加载都要 Tab 穿过整个导航栏。
1 | <body> |
注意:目标容器(<main>)需设置 tabindex="-1",使其可被 JavaScript 聚焦,但不进入 Tab 顺序。
3.5 焦点可见性::focus-visible
不要移除焦点轮廓(outline: none)除非提供了更明显的替代样式。使用 :focus-visible 可为键盘导航保留焦点指示,同时避免鼠标点击时出现不必要的轮廓。
1 | /* 仅当通过键盘聚焦时显示轮廓 */ |
4. 颜色对比度与可感知性
WCAG 2.1 AA 级要求:
- 普通文本:对比度至少 4.5:1。
- 大文本(18pt 或 14pt 加粗):对比度至少 3:1。
- UI 组件与图形:对比度至少 3:1。
可使用浏览器开发者工具(如 Chrome DevTools 的 CSS Overview)或在线工具(如 WebAIM Contrast Checker)检测对比度。
1 | /* ❌ 对比度不足:浅灰文字在白色背景上 */ |
注意:颜色不能作为传达信息的唯一手段。例如,错误提示不能仅用红色边框表示,还需配合图标和文字说明。
1 | <!-- ❌ 仅靠颜色区分 --> |
5. 屏幕阅读器测试与工具
5.1 常用屏幕阅读器
| 操作系统 | 屏幕阅读器 | 启动方式 |
|---|---|---|
| Windows | NVDA(免费) | Ctrl + Alt + N |
| Windows | JAWS(付费) | 桌面图标 |
| macOS | VoiceOver | Cmd + F5 |
| iOS | VoiceOver | 设置 → 辅助功能 |
| Android | TalkBack | 设置 → 辅助功能 |
5.2 浏览器开发者工具辅助
- Chrome DevTools:Lighthouse 审计(可自动检测对比度、ARIA 错误、标题层级等)。
- Firefox DevTools:可访问性检查器(显示元素的可访问名称、角色、状态)。
- axe DevTools 扩展:行业标准的自动化可访问性测试工具。
课后练习
一、概念自测(选择题 / 填空题)
(单选) 以下哪个属性用于为屏幕阅读器提供元素的补充描述信息,而不会覆盖其可访问名称?
A.aria-label
B.aria-labelledby
C.aria-describedby
D.aria-hidden(单选) 关于
tabindex的使用,下列哪项是正确的?
A. 使用tabindex="1"可让元素成为 Tab 顺序的第一个。
B. 使用tabindex="0"可将非交互元素加入自然 Tab 顺序。
C.tabindex="-1"的元素无法通过任何方式获得焦点。
D. 应优先使用正数tabindex来优化键盘导航。(填空) 要通知屏幕阅读器某区域内容已更新,且不打断当前播报,应设置
aria-live="______"。(多选) 以下哪些措施有助于提升键盘导航的可访问性?
A. 提供“跳转到主要内容”链接。
B. 在模态对话框打开时将焦点限制在对话框内。
C. 使用 CSSoutline: none移除所有焦点轮廓。
D. 确保所有交互元素可通过Tab键聚焦。
二、AI 编程任务:编写面向 AI 的提示词
场景:你需要实现一个可访问的“更多操作”下拉菜单组件。要求如下:
- 触发按钮显示文字“操作”和一个向下箭头图标。
- 菜单默认隐藏,点击按钮后展开,再次点击或点击外部区域时收起。
- 必须支持键盘操作:
Enter或Space展开/收起;展开后Tab可进入菜单项;Esc关闭菜单并将焦点返回按钮。 - 使用正确的 ARIA 属性:
aria-expanded、aria-controls、role="menu"、role="menuitem"。 - 箭头图标应设为
aria-hidden="true",并为按钮提供明确的aria-label。
任务要求:请写出一段完整的中文提示词,发送给 AI,使其生成符合上述要求的 HTML、CSS 和 JavaScript 代码。提示词中应明确指定可访问性要求(ARIA 属性、键盘交互、焦点管理)。
三、面试真题与参考答案
题目(腾讯前端面试题):
请解释 ARIA 属性
aria-label、aria-labelledby和aria-describedby的区别。在一个具有可见文本“删除”的按钮上,同时存在 SVG 图标,应如何使用这些属性以确保最佳可访问性?
参考答案:
1. 三者区别:
| 属性 | 作用 | 是否覆盖可访问名称 | 典型场景 |
|---|---|---|---|
aria-label |
直接提供一个字符串作为元素的可访问名称。 | 是 | 元素无可见文本,或可见文本不足以描述功能。 |
aria-labelledby |
通过空格分隔的 id 列表,引用其他元素的内容作为可访问名称。优先级高于 aria-label 和原生标签。 |
是 | 多个元素共同描述控件(如对话框标题 + 副标题),或标签已在别处存在。 |
aria-describedby |
引用其他元素的内容作为补充描述(不覆盖可访问名称)。阅读器在播报名称后,会追加播报描述内容。 | 否 | 提供帮助文本、格式提示、错误信息。 |
2. 按钮场景应用:
对于既有图标又有可见文字“删除”的按钮,最佳实践是让可见文字本身就作为可访问名称,无需额外 ARIA 属性。
1 | <button> |
- 图标设置
aria-hidden="true"防止被屏幕阅读器读出(避免重复)。 - 按钮内的“删除”文本自动成为可访问名称。
- 若图标后无可见文字,则需使用
aria-label="删除"。
3. 错误用法警示:
1 | <!-- ❌ 冗余:aria-label 与可见文字重复,且可能导致阅读器读出两遍 --> |
课后练习答案
一、概念自测答案
C
- 解析:
aria-describedby提供补充描述,不覆盖名称。aria-label和aria-labelledby会设置可访问名称。
- 解析:
B
- 解析:
tabindex="0"将元素加入自然 Tab 顺序。A 错误,正数tabindex破坏顺序;C 错误,-1可通过脚本聚焦;D 错误,应避免正数tabindex。
- 解析:
polite- 解析:
aria-live="polite"在用户空闲时播报,不打断当前操作。
- 解析:
A、B、D
- 解析:C 错误,移除焦点轮廓会严重损害键盘用户体验,应使用
:focus-visible提供替代样式。
- 解析:C 错误,移除焦点轮廓会严重损害键盘用户体验,应使用
二、AI 编程任务参考答案(提示词示例)
示例提示词:
“请实现一个可访问的下拉菜单组件(HTML + CSS + JavaScript)。要求:
- 触发按钮显示文字
操作和一个向下箭头图标(可用 SVG 或 Unicode 字符)。- 菜单初始隐藏,点击按钮切换展开/收起;点击外部区域自动收起。
- 键盘支持:
Enter或Space切换菜单;Escape关闭菜单并聚焦回按钮;菜单展开时按Tab可在菜单项间导航,Shift+Tab返回按钮。- 正确使用 ARIA:按钮设置
aria-expanded和aria-controls;菜单容器设置role="menu";菜单项设置role="menuitem";箭头图标aria-hidden="true";若按钮无可见文本则提供aria-label。- 使用 CSS
:focus-visible管理焦点样式,不直接移除outline。- 代码应有清晰注释。请输出完整可运行示例。”