可访问性体检:三个真问题与三次假警报
SEO 体检做久了会形成一个偏见:只关心搜索引擎看不看得懂。但同一批标记,屏幕阅读器和键盘用户也在用——比如 h1 结构、图片 alt、表单的无障碍名称。
这轮我给博客做了一次可访问性体检,查出三个真问题,也踩到三次「体检脚本自己骗人」。
六项检查,跑一遍就知道

| 检查 | 判据 |
|---|---|
lang | <html lang> 存在且正确 |
| 重复 id | 同一页面内 id 不重复(重复 id 是非法 HTML,锚点与 aria-labelledby 都会失准) |
| 图片 alt | 没有缺 alt 的 <img> |
| 无障碍名称 | 每个可聚焦元素都要有可见文本 / aria-label / title |
| 对比度 | 文字与背景的对比度 ≥ 4.5:1(大字 ≥ 3:1) |
| 减少动效 | CSS 里有 prefers-reduced-motion 分支 |
实现都是十几行 DOM 查询,不需要装任何依赖。
三个真问题
1. 每个页面都有两个 id="sidebar"。
外层网格容器(MainGridLayout)和侧栏组件内部(SideBar)用了同一个 id。重复 id 不会报错、页面也照常显示,但:
- 锚点跳转(
#sidebar)只会跳到第一个; aria-labelledby指向它时,读屏可能读到错的那个。
修法是把内层改成 id="sidebar-inner"——改之前先确认没有任何 CSS/JS 依赖 #sidebar(我扫了一遍,确实没有)。
2. 三个搜索框都没有无障碍名称。
它们只有 placeholder="搜索"。而 placeholder 有两个问题:输入内容后会消失,而且它不保证被当作可访问名称。读屏用户 Tab 到输入框时,听到的是一个没有标签的文本框。
修法是补 aria-label="搜索文章"——三个 input(桌面版、移动版、搜索面板内)都要补。
3. 键盘焦点环在深色背景上几乎看不见。
这条最隐蔽,因为我第一版体检把它判成了「通过」:outline: auto 1px —— 轮廓确实存在。但截图一看:
第二个 Tab 焦点: 归档 | outline: auto 1px rgb(16, 16, 16)
rgb(16,16,16) 是近黑色,压在本站 #0f1219 的深色背景上,等于没有。
修法是统一焦点样式,只在键盘导航时出现:
:where(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
outline: 2px solid var(--primary);
outline-offset: 2px;
border-radius: 4px;
}
改完再量,轮廓变成 solid 2px oklch(0.7 0.15 255)——主题蓝色,Tab 走一遍能清楚地看到光标在哪。

三次假警报(比真问题更值得记)

① 性能脚本报 LCP 1.9 秒。 真因是启动参数里带了 --host-resolver-rules=MAP * ~NOTFOUND(当初为了绕开本机代理),把所有第三方域名都解析成不存在——测出来的是「没有第三方的平行世界」。真实现场是 12 秒。
② SEO 脚本报 10 条「canonical 指向别处」。 真因是中文标签页的 canonical 是 percent-encoded 形式,两者等价,但脚本逐字符比较。改成比较前 unquote() 两边。
③ a11y 脚本报对比度 ratio 1.12(几乎全白 on 全白)。 真因是脚本没把文字颜色的 alpha 与背景合成:text-50 这类半透明文字、sr-only 这类收成 1px 的视觉隐藏元素全被算进了分母。
为什么假警报比漏报更危险:漏报只是没发现问题;假警报会让人去改本来没问题的东西——我差点为了「对比度不达标」去调深色主题的文字色,实际那两处一处是视觉隐藏的 h1、一处是半透明计数。
所以写体检脚本时,我给自己定了两条规矩:
- 每条判据都要能解释:「为什么这个元素不达标」要能打印出具体值(颜色、比例、坐标),而不是只给一个结论;
- 先怀疑脚本,再改代码:同一个「问题」出现在全站每个页面时,八成是判据写错了,而不是模板全错。
现在的基线
lang zh-CN
重复 id 0
图片缺 alt 0
无障碍名称 全部有(3 个搜索框已补 aria-label)
对比度 0 处不达标(大字 ≥3:1、正文 ≥4.5:1)
键盘焦点 主题色 2px 描边,Tab 全程可见
减少动效 prefers-reduced-motion 已支持
小结
可访问性体检的收益不只是「对残障用户友好」:
- 重复 id / 缺 alt / 无名称的按钮,对爬虫也是语义噪声;
- 可见焦点环让键盘用户能用——而这个站自己就是写技术的,读者里有一半会按 Tab;
- 更重要的是,它逼我把「我以为通过了」变成「量出来的结果」。
三行命令就能跑一遍,我把它和 SEO 体检放在同一个目录里,发文章前一起跑。
体检的具体判据与九类历史问题整理在站内 SEO 体检清单里,两套体检可以一起跑。
发现错误或想要改进这篇文章?
在 GitHub 上编辑此页文章修订历史 (1 次)
查看变更记录
docs(baseline): 第 30 轮终检——数字更新到最终状态,补第 28~30 轮小结
相关文章
- 24 篇文章的站内 SEO:一套可复跑的体检清单不靠站长后台、不装插件,写一个脚本扫构建产物就能查出大部分站内 SEO 问题。这篇是 24 篇文章、54 个页面跑下来整理出的八项检查、九类真实问题与修法,以及三条「体检本身也会骗你」的教训。
- 构建产物 16 MB,访客每次只下 28 KB统计 dist 目录发现 16 MB、313 个文件,其中 1.6 MB 的 Mermaid 图表引擎从来没被下载过。这篇把「部署体积」与「访客成本」分清:什么进了关键路径、什么按需加载、什么可以被缓存一年。
- 给静态博客的单篇文章上密码:构建期加密与浏览器解密静态站没有服务端,密码保护只能靠真加密:构建期用 PBKDF2-HMAC-SHA256 派生密钥、AES-256-GCM 加密正文,产物里只剩盐、IV 和密文。这篇讲清参数取舍、公开仓库的明文隔离、元数据泄露清单与解密后要重跑的初始化。