GJ 站长论坛・社区规则,请知晓

屏幕阅读器不友好修复

chinaz
chinaz 初级会员超兽战士
发布于 2026-10-10 09:00 ·2 浏览 ·0 回复

学完这篇,你能独立找出页面上"屏幕阅读器读不出来、读不明白"的地方,并动手改掉,最后用 NVDA 或 VoiceOver 亲耳验证确实修好了。

屏幕阅读器的"不友好"通常不是玄学,绝大多数问题就三类:元素没有正确的语义(用 div 装按钮)、可访问名称是空的(读出来只有"按钮"两个字)、状态变化没告诉辅助技术(弹窗开了但焦点还在背后)。下面的流程按"复现 → 定位 → 修 → 复测"走一遍就够了。

第一步:先装一个屏幕阅读器,亲耳听一遍

这一步的目标是让你亲眼看到问题,而不是靠猜。修之前先听,修之后再听,对比才明显。

Windows 上装 NVDA(官网 nvda-project.org,2024.4 之后的版本操作一致)。下载安装后按 Ctrl + Alt + N 启动,按 Insert + Q 退出。核心快捷键:Insert + F7 打开元素列表,H 跳到下一个标题,D 跳到下一个地标,B 跳到下一个按钮,F 跳到下一个表单字段,Insert + 空格 在"浏览模式"和"焦点模式"之间切换。

macOS 上用自带的 VoiceOver。按 Command + F5 开启(部分机型是 Control + Option + F5)。VO 键就是 Control + Option:VO + U 打开转子(Rotor),VO + 右箭头 逐个元素往下读,VO + 空格 激活元素。开关位置在「系统设置 > 辅助功能 > 语音 > VoiceOver」。

用键盘 Tab 把页面主流程走三遍:登录、下单、提交表单。边听边记:哪些地方读完就没有下文了?哪些读成"按钮 按钮"?哪些图片只读出"图片"?把 URL 和位置记下来。

注意:不要用鼠标配合屏幕阅读器测试。屏幕阅读器用户是纯键盘操作,鼠标点的路径和键盘 Tab 的路径经常完全不同。

第二步:用浏览器工具定位到具体节点

这一步要拿到"哪个元素、缺什么"的精确答案。

在 Chrome(120 以上版本)里按 F12 打开开发者工具。切到 Elements 面板,选中可疑节点,右侧标签栏找 Accessibility 面板,展开 Accessibility Tree,重点看三个字段:Name(可访问名称)、Role(角色)、State(状态)。如果 Name 是空的,屏幕阅读器就只能读出 Role,也就是干巴巴一句"按钮"。

批量扫描用 axe DevTools 扩展(Chrome 应用商店搜 "axe DevTools Accessibility Testing")。装好后 F12 → axe DevTools 标签 → 点 Scan ALL of my page,它会直接告诉你哪个 CSS 选择器违反了哪条规则,并给出修改建议。也可以切到 Lighthouse 面板,只勾 Accessibility,点 Analyze page load 看汇总报告。

注意:axe 能自动发现的大约只占全部无障碍问题的三分之一。它查不出"读起来顺序乱了""这段话听不懂"这类问题,这些只能靠第一步的人耳判断。

第三步:把"假按钮、假链接"换成真标签

这一步解决最常见的一类问题。目标是把装饰性的 div 换成有语义的原生标签,让屏幕阅读器正确播报角色并支持键盘操作。

典型反例:

<div class="btn" onclick="submitForm()">提交</div>

改成:

<button type="button" onclick="submitForm()">提交</button>

用 div 的问题很实在:不能 Tab 聚焦、按回车和空格没反应、屏幕阅读器读成"文本"而不是"按钮"。

如果因为历史代码实在改不动,最低限度补上角色和键盘事件:

<div role="button" tabindex="0"
     onclick="submitForm()"
     onkeydown="if(event.key==='Enter'||event.key===' '){event.preventDefault();submitForm();}">
  提交
</div>

但这只是兜底。同类的还有:页面标题不要用 <div class="title"> 代替 <h2>,因为屏幕阅读器用户靠 H 键按标题层级跳转;页面主体用 <main>、导航用 <nav>、页脚用 <footer>。同一页面有多个 <nav> 时,用 aria-label 区分:<nav aria-label="主导航"> 和 <nav aria-label="面包屑">。

注意:标题层级不能跳级。h1 直接跳到 h3 会让用户以为自己漏听了一段。

第四步:给图片和图标按钮补上"能读出来的名字"

这一步解决"读出来是空的"问题。

图片分两种。有信息量的图片写实际内容,比如 <img src="chart.png" alt="2024 年 1 月至 6 月订单量增长 32%">。纯装饰的图片写空 alt,比如 <img src="line.png" alt="">。

注意:装饰图片必须写 alt="",而不是把 alt 属性整个删掉。省略 alt 时,部分屏幕阅读器会把文件名念出来,比如"line 点 png"。

图标按钮:

<button class="icon" aria-label="关闭对话框">
  <svg aria-hidden="true" focusable="false">...</svg>
</button>

aria-hidden="true" 让 SVG 本身不被读,aria-label 给按钮提供名字。如果按钮里已有可见文字,就不要再加 aria-label 覆盖它,否则语音控制用户喊"点击提交"会失效(这条规则叫 Label in Name)。没有可见文字的场景,也可以用一个视觉隐藏的 span:

.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0;}

第五步:让表单的标签、必填、报错都能被读出来

这一步的重点是:每个输入框都要有能被读出来的名字,报错要能被朗读。

<label for="email">邮箱</label>
<input id="email" type="email" required aria-describedby="email-hint">
<p id="email-hint">我们只会用它发送订单通知</p>

label 的 for 必须和 input 的 id 完全一致,这是最容易被忽略又最容易修的。

注意:不能用 placeholder 代替 label。placeholder 在输入第一个字符后就消失了,而且不少屏幕阅读器对它的播报不稳定。

校验失败时:

<input id="email" type="email" aria-invalid="true" aria-describedby="email-err">
<p id="email-err" role="alert">邮箱格式不正确,请检查是否缺少 @ 符号</p>

role="alert" 会让这段文字在插入时被立即朗读。单选和复选成组时,用 <fieldset> 包起来,<legend> 写组名,例如"配送方式"。

第六步:处理动态更新和弹窗焦点

这一步解决"页面变了但屏幕阅读器不知道"的问题。

Toast、加载状态这类提示,放在一个预先就存在的容器里:

<div aria-live="polite" id="toast"></div>

普通提示用 polite,紧急错误用 assertive。

注意:aria-live 容器必须在页面加载时就存在于 DOM 里。如果先 remove() 再 append(),或者动态创建整个容器再插入,很多时候朗读不会触发。

弹窗用原生 <dialog> 元素最省事:

dialog.showModal(); // 自带焦点陷阱和 Escape 关闭
dialog.close();

用自定义弹窗的话,必须手动做四件事:打开时把焦点移到对话框内(dialogEl.focus()),关闭时把焦点还给触发它的那个按钮,加 role="dialog" 和 aria-modal="true" 以及 aria-labelledby 指向标题,背景内容加 inert 属性(Chrome 102+ 支持)。

单页应用路由切换后,把焦点移到新页面的 <h1 tabindex="-1"> 上,同时更新 document.title,否则用户不知道页面已经换了。

第七步:加进回归流程,别改回去

这一步保证修好的东西不被下一次提交破坏。

先做键盘走查:只用 Tab、Shift + Tab、Enter、空格、方向键走完主流程,确认每一步焦点环都看得见。项目里如果写过 outline: none,换成 :focus-visible 自定义样式,不要直接删掉。

再上自动化。在项目里装 axe 的命令行版本:

npm i -D @axe-core/cli
npx axe https://你的站点地址 --exit

--exit 让发现问题时返回非零退出码,方便接进 CI。组件库项目可以用 jest-axe,端到端用 @axe-core/playwright。CI 里跑 pa11y-ci 也行。

最后回到第一步,用 NVDA 或 VoiceOver 把主流程再听一遍,确认原来记下的那几个问题都不再出现。

注意:自动化工具过了不等于修完了。发布前的人工听读不能省。

小结

  • 先复现再修:用 NVDA(Insert + F7)或 VoiceOver(VO + U)纯键盘走一遍主流程,记录问题位置。
  • 定位靠工具:Chrome DevTools 的 Accessibility 面板看 Name/Role/State,axe DevTools 批量扫,Lighthouse 出汇总。
  • 三大修复方向:换原生标签(div → button、h2)、补可访问名称(alt、aria-label、label for)、通知状态变化(aria-live、焦点管理)。
  • 空 alt 要写成 alt="",不能省略属性;aria-label 不要覆盖已有的可见文字。
  • 修完必须复测,并接进 CI(axe CLI / jest-axe / pa11y-ci),但人工听读不能省。
版权声明:本文来自 GJ站长论坛《屏幕阅读器不友好修复》
原文链接:https://www.gj0.com/thread-1267.html
转载请注明出处并保留本声明;内容仅代表作者观点,与本站立场无关。若本文涉嫌侵权,请联系本站处理。

全部回复 0

还没有回复,来抢沙发~