Web 无障碍最佳实践:从可访问性树到实战代码 By youhao2cj | 2026年9月29日 0 Comment 为什么你的网站需要无障碍支持? 想象一下,你正在使用一个只能通过键盘操作的银行网站,或者你是一位视障用户,依赖屏幕阅读器浏览新闻。如果网站没有考虑无障碍(Accessibility,简称 A11y),这些用户将无法完成任务。全球有超过 10 亿残障人士,无障碍不仅是道德责任,也是法律要求(如 WCAG、ADA)。更重要的是,良好的无障碍实践能提升所有用户的体验:更清晰的语义、更好的键盘支持、更快的加载速度。 本文将从可访问性树(Accessibility Tree)讲起,通过一个完整的实战案例,带你一步步实现符合 WCAG 2.1 AA 标准的 Web 应用。 1. 理解可访问性树:浏览器如何“看”你的页面 浏览器在渲染 DOM 的同时,会构建一棵可访问性树(Accessibility Tree),辅助技术(如屏幕阅读器)正是通过这棵树来理解页面内容。如果元素没有正确的角色(role)、名称(name)和状态(state),屏幕阅读器就无法传达信息。 关键点:语义化 HTML 元素(如 、 、)会自动映射到可访问性树中的角色。而 和 默认没有角色,需要手动添加 ARIA 属性。 2. 语义化 HTML:最基础也最重要的实践 2.1 使用正确的元素 错误示例: <div onclick="submitForm()">提交</div> 正确示例: <button type="submit">提交</button> 自带键盘支持(Enter/Space 触发)、焦点管理和正确的角色(button)。而 需要手动添加 tabindex="0"、role="button" 和键盘事件监听,且容易遗漏。 2.2 文档结构标签 使用 、 、、 、 等地标元素,屏幕阅读器用户可以快速跳转到不同区域。 <body> <header> <h1>我的网站</h1> </header> <nav aria-label="主导航"> <ul> <li><a href="/">首页</a></li> <li><a href="/about">关于</a></li> </ul> </nav> <main> <h2>欢迎</h2> <p>内容...</p> </main> <footer> <p>© 2025</p> </footer> </body> 💡 注意:一个页面只应有一个 ,且每个 最好用 aria-label 区分(如“主导航”、“面包屑”)。 3. ARIA 属性:用对是锦上添花,用错是雪上加霜 ARIA(Accessible Rich Internet Applications)可以补充语义,但第一条规则是:不要用 ARIA 替代原生 HTML。 3.1 常见 ARIA 属性 aria-label:为元素提供文本标签(当视觉文本不充分时)。 aria-labelledby:通过其他元素的 id 来命名当前元素。 aria-describedby:提供额外描述。 aria-hidden="true":对辅助技术隐藏元素(用于装饰性内容)。 aria-live:动态内容更新时通知屏幕阅读器。 3.2 实战:可访问的模态对话框 模态对话框是无障碍的重灾区。下面是一个符合 WAI-ARIA 规范的实现: <div role="dialog" aria-modal="true" aria-labelledby="dialog-title" id="dialog"> <h2 id="dialog-title">确认删除</h2> <p>确定要删除这条记录吗?此操作不可撤销。</p> <button id="cancel-btn">取消</button> <button id="confirm-btn">删除</button> </div> const dialog = document.getElementById('dialog'); const cancelBtn = document.getElementById('cancel-btn'); const confirmBtn = document.getElementById('confirm-btn'); const previousFocus = document.activeElement; function openDialog() { dialog.style.display = 'block'; dialog.setAttribute('aria-hidden', 'false'); // 将焦点移到对话框内第一个可聚焦元素 cancelBtn.focus(); // 监听键盘事件 document.addEventListener('keydown', handleKeyDown); } function closeDialog() { dialog.style.display = 'none'; dialog.setAttribute('aria-hidden', 'true'); document.removeEventListener('keydown', handleKeyDown); // 恢复之前的焦点 if (previousFocus) previousFocus.focus(); } function handleKeyDown(e) { if (e.key === 'Escape') { closeDialog(); } // 焦点陷阱:Tab 键循环在对话框内 if (e.key === 'Tab') { const focusable = dialog.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'); const first = focusable[0]; const last = focusable[focusable.length - 1]; if (e.shiftKey && document.activeElement === first) { e.preventDefault(); last.focus(); } else if (!e.shiftKey && document.activeElement === last) { e.preventDefault(); first.focus(); } } } cancelBtn.addEventListener('click', closeDialog); confirmBtn.addEventListener('click', () => { // 执行删除操作 closeDialog(); }); // 打开对话框的按钮 openDialog(); 💡 注意:模态对话框必须管理焦点,否则屏幕阅读器用户会迷失在背景内容中。aria-modal="true" 告诉辅助技术忽略背景内容。 4. 键盘导航:让所有功能可触达 所有交互元素必须可以通过键盘访问。默认情况下,、、 等是可聚焦的,但自定义组件需要显式设置 tabindex。 4.1 管理 tabindex tabindex="0":元素可聚焦,且按 DOM 顺序参与 Tab 导航。 tabindex="-1":元素可聚焦,但只能通过 JavaScript 聚焦(用于程序化焦点管理)。 避免使用 tabindex 大于 0 的值,它会打乱自然顺序。 4.2 跳过导航链接 为键盘用户提供“跳到主内容”的链接: <a href="#main-content" class="skip-link">跳到主内容</a> <!-- 其他导航 --> <main id="main-content">...</main> .skip-link { position: absolute; top: -40px; left: 0; background: #000; color: #fff; padding: 8px; z-index: 100; } .skip-link:focus { top: 0; } 5. 颜色对比度与视觉设计 WCAG 2.1 AA 要求正常文本对比度至少 4.5:1,大文本(18pt 或 14pt 粗体)至少 3:1。使用工具如 WebAIM Contrast Checker 验证。 /* 不好的例子:对比度不足 */ .low-contrast { color: #999; background: #fff; /* 对比度约 2.8:1 */ } /* 好的例子 */ .good-contrast { color: #333; background: #fff; /* 对比度约 12.6:1 */ } 💡 注意:不要仅靠颜色传达信息(如红色表示错误)。应同时使用图标或文本。 6. 表单无障碍:标签、错误提示与分组 6.1 显式关联标签 <label for="username">用户名</label> <input type="text" id="username" name="username" aria-required="true"> 6.2 错误提示 <label for="email">邮箱</label> <input type="email" id="email" aria-describedby="email-error" aria-invalid="true"> <span id="email-error" role="alert">请输入有效的邮箱地址</span> role="alert" 会立即通知屏幕阅读器。 6.3 字段集分组 <fieldset> <legend>选择你的兴趣</legend> <input type="checkbox" id="coding" name="interest" value="coding"> <label for="coding">编程</label> <input type="checkbox" id="design" name="interest" value="design"> <label for="design">设计</label> </fieldset> 7. 动态内容与 ARIA Live Regions 当页面内容动态更新(如搜索结果、通知),使用 aria-live 让屏幕阅读器播报。 <div aria-live="polite" id="status"></div> <script> // 几秒后更新 setTimeout(() => { document.getElementById('status').textContent = '已保存 3 条记录'; }, 2000); </script> polite:等待用户空闲时播报。 assertive:立即打断播报(用于紧急错误)。 8. 测试你的无障碍实现 8.1 自动化工具 axe DevTools:浏览器扩展,快速扫描问题。 Lighthouse:Chrome 内置,生成无障碍报告。 Pa11y:命令行工具,可集成 CI。 8.2 手动测试 仅用键盘操作:Tab、Shift+Tab、Enter、Space、方向键。 使用屏幕阅读器:NVDA(Windows)、VoiceOver(Mac)、Orca(Linux)。 放大到 200% 检查布局。 8.3 在 CI 中加入无障碍测试 // package.json { "scripts": { "test:a11y": "pa11y-ci --sitemap https://example.com/sitemap.xml" } } 9. 常见坑与最佳实践总结 不要用 ARIA 修复语义化 HTML 的问题:优先使用原生元素。 不要隐藏焦点轮廓:除非提供替代样式。 不要滥用 aria-hidden:它会隐藏内容,但可能让可聚焦元素不可见。 确保所有功能可通过键盘访问。 提供足够的颜色对比度。 为图片提供 alt 文本:装饰性图片用 alt=""。 测试、测试、再测试:自动化 + 手动。 10. 延伸阅读 WCAG 2.1 快速参考 MDN 无障碍指南 A11y Project Checklist 无障碍不是一次性任务,而是持续的过程。从今天开始,在你的项目中加入一个跳过链接、检查对比度、用键盘走一遍流程,你就已经迈出了重要一步。 Post navigation ← Chrome DevTools 高级调试技巧:从断点到性能剖析的实战指南 数据库分库分表设计与实践:从理论到ShardingSphere落地 →