lighthouse仅捕获静态快照,无法覆盖用户交互、spa路由切换及动态状态变化,故需结合真实路径埋点与performanceobserver监控语义一致性。

为什么不能只靠 Lighthouse 抓一次报告
Lighthouse 的 audit 结果是快照式、单页、无交互的——它测的是“加载那一刻的静态结构”,而真实用户会点击、滚动、切换 Tab、输入表单、遭遇网络抖动。一个 aria-label 漏写可能在 Lighthouse 里得 98 分,但用户用键盘 tab 到按钮时却完全不知道这是干啥的。更麻烦的是:SPA 路由切换后,新页面的语义结构(比如缺失 main、重复 h1)根本不会被 Lighthouse 自动捕获。
必须监听真实用户路径中的可访问性断点
可用性 ≠ 可访问性。监控可访问性,核心是跟踪用户是否“能完成关键操作”,而不是“DOM 是否符合 WCAG 清单”。以下三类事件必须主动埋点:
-
focus进入关键控件(如搜索框、提交按钮)但document.activeElement没有可读的aria-label或innerText→ 记为「焦点语义缺失」 -
change触发后,关联的aria-live区域 500ms 内未更新文本内容 → 记为「实时反馈中断」 -
click后 2s 内,document.visibilityState仍为visible但document.querySelector('[role="alert"]')未出现且 URL 未跳转 → 记为「操作无响应」
用 Navigation Timing + Element Timing 捕获软失败
很多可访问性问题发生在“页面已渲染但功能不可用”阶段,比如:
- 屏幕阅读器能读出按钮文字,但
button.disabled = true却没同步更新aria-disabled→ 用户以为能点,实际点不动 - 动态加载的表格用
aria-rowcount声明了 10 行,但 JS 渲染只 append 了 3 个tr→ 辅助技术导航直接跳空
解决方案不是加更多 aria- 属性,而是用 PerformanceObserver 监听 element 类型条目,比对 entry.startTime 和 entry.duration,再结合 getComputedRole()(需 polyfill)验证角色与状态一致性。例如:
const obs = new PerformanceObserver((list) => {
list.getEntries().forEach(entry => {
if (entry.entryType === 'element' && entry.name === 'submit-btn') {
const el = document.getElementById('submit-btn');
if (el?.disabled && el.getAttribute('aria-disabled') !== 'true') {
reportA11yIssue('aria-disabled-mismatch', entry);
}
}
});
});
obs.observe({ entryTypes: ['element'] });
上报数据必须带上下文,否则无法归因
只上报 "aria-label missing" 没用。真正要传的字段包括:
-
url:当前完整路径(含 hash) -
userAgent:提取navigator.userAgent中的辅助技术标识(如AppleWebKit/... VoiceOver、Gecko/... NVDA) -
interactionPath:从页面加载到触发问题的操作链,例如["load", "tab", "tab", "enter"] -
domPath:用el.ariaLabel || el.innerText || el.textContent拼接的最简可读路径,避免传整个 outerHTML
特别注意:不要在 visibilitychange 为 hidden 时停止上报——有些屏幕阅读器用户会长时间停留在同一页面,后台运行的定时检测(如每 30s 扫描一次焦点元素)反而更能暴露长期挂起的语义缺陷。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











