无障碍访问
前言
Web 无障碍(Accessibility,简称 a11y)是指让尽可能多的人能够访问和使用 Web 内容,无论他们是否存在身体、认知或技术上的障碍。根据世界卫生组织的统计,全球约有 15% 的人口存在某种形式的残疾。这意味着,每 100 个访问你网站的用户中,就有大约 15 人可能因无障碍问题而无法正常获得信息或完成操作。
随着各国对无障碍法规的日益重视——欧盟的《欧洲无障碍法案》(European Accessibility Act)、美国的《美国残疾人法案》(ADA)、中国的《无障碍环境建设法》——无障碍建设已从"可选项"变为"必选项"。在国内,2023 年 9 月起施行的《无障碍环境建设法》更是明确将互联网网站和移动应用纳入监管范围。
本文将围绕 WCAG 标准、ARIA 属性、键盘导航、屏幕阅读器适配以及常见问题的修复,系统性地介绍前端无障碍开发的核心理念与实践方法。
一、WCAG 标准核心原则
WCAG(Web Content Accessibility Guidelines)是 W3C 发布的一套被广泛认可的 Web 无障碍标准。目前主流版本是 WCAG 2.1,WCAG 2.2 于 2023 年 10 月正式发布,新增了焦点外观(Focus Appearance)、拖拽操作(Dragging)等成功标准。WCAG 的核心框架建立在四大原则之上,简称 POUR:
1.1 可感知(Perceivable)
信息与用户界面组件必须以用户可以感知的方式呈现。这意味着内容不能对用户的所有感官都不可见。关键要求包括:
- 为所有非文本内容提供替代文本:图片需要
alt属性、视频需要字幕和音频描述、图标按钮需要aria-label。 - 提供同步媒体替代方案:为预录视频提供手语翻译或文字脚本。
- 内容可适应不同呈现方式:内容在不丢失信息的情况下,可以更简洁的方式呈现(如通过屏幕阅读器朗读)。
- 颜色不能作为唯一传达信息的方式:错误提示不能仅用红色标记,需要配合文字或图标。
- 文本对比度至少达到 4.5:1(AA 级)或 3:1(AAA 级用于大号文本)。
- 音频内容可控制:自动播放的音频超过 3 秒必须有暂停/停止机制。
1.2 可操作(Operable)
用户界面组件和导航必须可操作。这要求所有交互对所有输入方式都可用:
- 所有功能均可通过键盘操作:不存在需要鼠标才能触发的交互。
- 用户有充足时间阅读和操作:超时机制应当可调、可延或可关。
- 内容不会引发癫痫或生理不适:每秒闪烁次数不得超过 3 次。
- 提供帮助用户导航和定位的机制:如面包屑导航、跳过导航链接、清晰的页面标题。
- 提供多种查找内容的方式:搜索、站点地图、导航菜单等多种路径。
- 焦点顺序有意义且可预测:Tab 键导航应当遵循视觉顺序。
- 焦点不因交互而意外跳转:模态框打开时焦点应锁定在模态框内。
1.3 可理解(Understandable)
信息与用户界面操作必须可理解:
- 页面语言可通过
lang属性声明:<html lang="zh-CN">。 - 组件操作方式的一致性和可预测性:同一功能在不同页面不应使用不同交互方式。
- 帮助用户避免和纠正错误:表单验证错误需要提供清晰的描述和修正建议。
- 提供输入帮助和说明:复杂输入框需要
aria-describedby关联帮助文本。
1.4 鲁棒性(Robust)
内容必须足够健壮,能够被各种用户代理(包括辅助技术)可靠地解释:
- 使用合法的 HTML 标记:标签必须正确闭合,嵌套关系正确。
- ARIA 属性使用正确:不滥用、不重复声明已由原生 HTML 语义表达的信息。
- 状态变化可通过程序化方式获知:使用
aria-live区域动态通知内容变化。
二、ARIA 属性使用指南
ARIA(Accessible Rich Internet Applications)是一套为动态内容和高级用户界面组件提供无障碍支持的 HTML 属性规范。它可以弥补原生 HTML 在表达组件状态和行为方面的不足。
2.1 角色(Role)
role 属性用于定义元素的角色类型,告诉辅助技术这个元素的用途:
<!-- 将 div 声明为按钮 -->
<div role="button" tabindex="0" onclick="handleClick()">提交</div>
<!-- 声明导航区域 -->
<nav role="navigation">...</nav>
<!-- 声明对话框 -->
<div role="dialog" aria-labelledby="dialog-title" aria-modal="true">...</div>重要原则:优先使用原生 HTML 语义元素(如 <button>、<nav>、<dialog>),只在原生语义不足时才使用 role。过度使用 ARIA 角色反而可能导致混乱。
2.2 状态和属性
aria-label:为元素提供可访问的名称,用于屏幕阅读器朗读。适用于图标按钮、无文本链接等场景。html<button aria-label="关闭对话框">✕</button>aria-labelledby:引用另一个元素的 ID 作为当前元素的标签。常用于表单控件和对话框。html<h2 id="dialog-title">确认删除</h2> <div role="dialog" aria-labelledby="dialog-title">...</div>aria-describedby:提供更详细的描述,通常关联帮助文本或错误提示。html<input type="password" aria-describedby="pwd-hint" /> <span id="pwd-hint">密码至少 8 位,包含字母和数字</span>aria-hidden:将元素从无障碍树中移除,让屏幕阅读器忽略它。常用于装饰性图标、纯展示元素。html<i class="fas fa-search" aria-hidden="true"></i>aria-expanded:指示可展开组件(手风琴、下拉菜单)的展开状态。html<button aria-expanded="false" aria-controls="menu-content">菜单</button>aria-live:定义动态内容区域,屏幕阅读器会在内容变化时自动播报。值有off(默认)、polite(安静时播报)、assertive(立即播报)。html<div aria-live="polite" id="notification">您有 3 条新消息</div>aria-current:指示当前选中的项目(如当前导航页、当前步骤)。html<a href="/home" aria-current="page">首页</a>
2.3 ARIA 最佳实践
- 不要改动原生语义:在
<button>上使用role="link"是错误用法。 - 不要重复信息:已有
alt属性的图片不需要再添加aria-label。 - 键盘交互必须匹配角色:声明
role="button"的元素必须支持 Enter 和 Space 键激活。 - 状态属性保持同步:
aria-expanded、aria-pressed等状态属性必须与实际可见状态一致。 - 使用
aria-live但不过度:大段内容更新应使用polite,仅关键提示使用assertive。
三、键盘导航设计
3.1 Tab 键顺序
键盘导航的核心是 Tab 键(向前)和 Shift+Tab(向后)在可聚焦元素之间移动焦点。默认情况下,以下元素可以聚焦:
<a>(有href)<button><input><select><textarea><iframe>
通过设置 tabindex 可以控制元素的聚焦行为:
tabindex="0":元素可通过键盘聚焦,按 DOM 顺序排列。tabindex="-1":元素可通过编程方式聚焦(element.focus()),但不进入 Tab 键顺序。tabindex="1"(或任意正数):应避免使用,会破坏常规的 Tab 顺序。
<!-- 好的实践:维持自然 Tab 顺序 -->
<button>确定</button>
<button tabindex="-1">此按钮只能通过 JS 聚焦</button>
<!-- 坏的实践:正数 tabindex 制造混乱 -->
<input tabindex="3" />
<input tabindex="1" />3.2 焦点管理
良好的焦点管理是无障碍体验的关键:
- 页面加载时:焦点应置于主内容区,或通过"跳过导航"链接让用户选择。
- 模态框打开时:焦点应移至模态框内的第一个可聚焦元素,并锁定在模态框内(焦点陷阱)。
- 模态框关闭时:焦点应回到触发模态框的元素。
- 动态内容插入时:新内容应接管焦点,或在
aria-live区域中播报。 - 单页应用路由切换时:焦点应移回页面顶部的主标题。
3.3 快捷键设计
对于复杂应用,提供合理的快捷键可以显著提升键盘用户的效率:
- 常用快捷键:使用
accesskey属性定义激活键,但需注意避免与浏览器/屏幕阅读器快捷键冲突。 - 自定义快捷键系统:对于 Web 应用(如邮件客户端、IDE),可提供类似
Ctrl+K的命令面板。 - 组合键的标识:清晰地展示快捷键组合,如"G + N 新建笔记"。
<!-- accesskey 示例 -->
<button accesskey="s">保存 (Alt+S)</button>WAI-ARIA 创作实践中定义的键盘交互模式:
| 组件类型 | 键盘操作 |
|---|---|
| 按钮 | Enter/Space 激活 |
| 链接 | Enter 跳转 |
| 单选/复选框 | Space 切换,方向键切换选项 |
| 下拉菜单 | Enter 展开,方向键导航,Escape 收起 |
| 滑块 | 方向键调整值,Home/End 到极值 |
| Tab 面板 | Tab 切换到面板区域,方向键切换标签页 |
| 树形菜单 | 方向键导航,左右键展开/折叠,Home/End 到首尾 |
| 对话框 | Escape 关闭,Tab 在内部循环聚焦 |
四、屏幕阅读器适配
4.1 工作原理
屏幕阅读器(如 NVDA、JAWS、VoiceOver、TalkBack)将网页内容转换为语音输出或盲文。它们通过浏览器的无障碍树(Accessibility Tree)来获取页面信息。无障碍树是根据 DOM 树和 ARIA 属性生成的简化版本。
4.2 适配要点
语义化 HTML 是第一道防线:正确的标题层级(
h1→h6)、列表、表格、表单标签是屏幕阅读器理解页面的基础。使用
sr-only类提供额外信息:在不影响视觉布局的前提下,为屏幕阅读器提供上下文:css.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); border: 0; }html<button> <i class="fas fa-edit" aria-hidden="true"></i> <span class="sr-only">编辑文章</span> </button>页面区域使用 Landmark:使用
<header>、<nav>、<main>、<aside>、<footer>等结构元素,让屏幕阅读器用户快速跳转到目标区域。动态更新使用
aria-live:通知区域、错误提示、加载状态变化都需要通过aria-live区域告知屏幕阅读器。表格数据使用
<th>和scope:正确标记表头与数据单元格的关联关系。表单字段关联
<label>:每个输入控件都应有对应的<label>,或使用aria-label。
4.3 常见屏幕阅读器测试
| 屏幕阅读器 | 操作系统 | 浏览器 | 备注 |
|---|---|---|---|
| NVDA | Windows | Firefox/Chrome | 免费开源,国内开发者首选的测试工具 |
| JAWS | Windows | Chrome | 商业软件,市场份额最高 |
| VoiceOver | macOS/iOS | Safari | 系统内置,Apple 设备用户常用 |
| TalkBack | Android | Chrome | 系统内置 |
| Narrator | Windows | Edge | 系统内置,基础可用 |
五、常见无障碍问题及修复
5.1 缺失图片替代文本
问题:<img> 标签缺少 alt 属性,屏幕阅读器会朗读文件名或完全跳过。
<!-- 问题代码 -->
<img src="chart.png" />
<!-- 修复方案:关键图片提供描述性 alt -->
<img src="chart.png" alt="2024 年 Q1 到 Q4 各季度营收柱状图" />
<!-- 装饰性图片使用空 alt -->
<img src="decoration.png" alt="" />5.2 表单标签缺失
问题:输入框没有关联 <label>,屏幕阅读器无法知道输入框的用途。
<!-- 问题代码 -->
<input type="text" placeholder="请输入用户名" />
<!-- 修复方案 -->
<label for="username">用户名</label>
<input type="text" id="username" />
<!-- 或使用 aria-label -->
<input type="text" aria-label="用户名" />5.3 颜色对比度不足
问题:文字颜色与背景色对比度低于 4.5:1,低视力用户难以辨认。
修复方案:使用对比度检测工具(WebAIM Contrast Checker)验证,调整颜色值确保满足 AA 级标准。避免在有色背景上使用浅色文字,尤其是当背景为渐变或图案时。
5.4 非语义化交互元素
问题:使用 <div> 或 <span> 模拟按钮,但没有提供 role="button" 和键盘事件支持。
<!-- 问题代码 -->
<div onclick="submit()">提交</div>
<!-- 修复方案 -->
<button onclick="submit()">提交</button>
<!-- 如果必须用 div -->
<div role="button" tabindex="0" onclick="submit()" onkeydown="if(event.key==='Enter'||event.key===' ') submit()">提交</div>5.5 焦点指示不明显
问题:移除了 :focus 的默认轮廓(outline: none)但没有提供替代的焦点样式。
/* 问题 CSS */
button:focus {
outline: none;
}
/* 修复方案:提供自定义焦点样式 */
button:focus-visible {
outline: 2px solid #4A90D9;
outline-offset: 2px;
}5.6 动态内容未通知
问题:页面内容动态更新时(如 AJAX 加载结果、表单验证错误),屏幕阅读器用户无法感知。
修复方案:使用 aria-live 区域包裹动态内容,或在内容更新后通过 JavaScript 将焦点移动到新内容上。
<div aria-live="polite" id="search-results"></div>六、总结
Web 无障碍不是一项可选的"附加功能",而是高质量 Web 应用的基本属性。它不是一个一次性改造项目,而应融入日常的开发流程中。实践中建议遵循以下原则:
- 优先使用原生 HTML 语义:HTML 本身内置了丰富的无障碍支持,充分利用可以避免大量额外工作。
- 渐进增强:先确保基础功能可用,再通过 ARIA 增强交互体验。
- 使用自动化工具:集成 axe-core、Lighthouse、WAVE 等工具到 CI/CD 流程中,将无障碍检测作为质量门禁的一环。
- 手动测试不可替代:自动化工具只能发现约 30% 的无障碍问题,键盘导航测试和屏幕阅读器测试必不可少。
- 引入无障碍设计审查:在 UI 设计稿阶段就进行无障碍评估,避免后期返工。
无障碍的本质是包容性设计——当我们在为特殊群体扫清障碍时,最终受益的是所有人。键盘快捷键、高对比度模式、清晰的文案、合理的交互设计,这些都让产品变得更好用。