想象你刚刚接管了一栋住满各种客人的摩天大厦。有视障的住户需要盲文按钮,色弱的访客分不清红绿指示灯,坐轮椅的业主得靠坡道替代台阶——而你手下的物业团队只有四个人,根本不可能蹲在每层楼梯口逐一检查。这时,你买了一台名叫“小斧”的巡检机器人。它揣着一本厚厚的检查清单,沿着楼层一寸寸扫过,发现盲道被占用就贴橙色标签,应急灯亮度不足就贴黄色标签,下班前交给你一份带照片、带坐标的巡检报告。你只需要照着标签颜色安排工期,修完请它复检一遍。 这篇教程的主角,就是网页世界的“小斧”——axe-core。它把你的页面当作一栋大厦,自动检查按钮有没有名字、图片有没有替代文字、文字颜色和背景对比够不够清楚、无障碍标签有没有被滥用,最后整理成一份结构化的 JSON 报告。读懂这份报告,并把这台机器人排进你的日常巡逻路线——也就是 CI/CD 流水线——你就掌握了前端无障碍(A11y)自动化检测的核心能力。
无障碍合规正成为产品验收与法律法规的硬性要求。掌握 axe-core 自动化检测,等于为团队装上一位 24 小时在线的无障碍巡检员,让隐藏在前端页面里的问题在发布前就现出原形。这是前端工程师、测试工程师与开发者工具团队必备的工程素养。
可视化演示
展示axe-core无障碍检测规则、DOM树遍历、违规严重度分级及CI流水线集成机制,含粒子场背景和发光动画。
本节配有交互动效(进入视口后自动加载,约 3KB)。
A11y检测基础
大厦的坡道不只是给轮椅用的——推婴儿车的妈妈、拖着行李箱的旅客、送货的搬运师傅,全都受益于此。网页也一样。无障碍(A11y)这个词,英文是 Accessibility,取首字母 A、末字母 y、中间 11 个字母,所以缩写成 A11y。它的核心思想是:不论视力、听力、行动力、认知能力如何,所有人都能平等地获取信息和完成任务。
网页世界的“建筑规范”叫做 WCAG——Web 内容无障碍指南,由 W3C 发布。它像一册厚厚的消防条例,规定了文本对比度、键盘可达性、表单可辨识性等标准。问题是,人工对照这册条例逐页检查,就像物业经理拿着 200 页手册亲自爬遍整栋楼的每一层,费时费力还容易遗漏。
axe-core 就是来解决这个痛点的。它是 Deque Systems 开源的自动化无障碍检测引擎,能在毫秒级扫描完一个页面,输出标准化的 JSON 报告。它被公认为业界事实标准,因为规则覆盖面广、误报率低,还深度支持 CI 集成。自动化检测的价值在于“持续且一致”——人会有情绪波动、会有看漏,机器人却永远按同一把尺子量每一页。
一座楼如果坡道、盲道、标识全部合规,每一位访客都会感受到这份用心。同样地,当你的页面通过 axe-core 全量扫描,其实是在向每一位用户传递一个信号:这个产品,欢迎所有人。
- covered_rules(已覆盖规则数)你的项目实际触发的、且 axe-core 能够自动判断的规则数量。比如页面上有图片,那么“图片必须带 alt 文本”这条规则就算被覆盖。
- total_rules(规则总数)axe-core 内置的全部规则总量,目前超过 90 条。其中包括 WCAG 各级别映射和最佳实践类规则。
- × 100%把比值转成百分比,方便团队设定质量门禁阈值,例如“A11y 覆盖率不得低于 80%”。
axe-core原理
小斧是怎么工作的?它不是魔法,而是一个结构清晰的三段式巡检流程:睁眼——看到全楼所有角落;对照——把看到的东西和检查清单比对;贴条——给不合格的地方登记造册。
对应到技术上,axe-core 的执行入口是 axe.run()。它接受三个参数:上下文(你要查哪一栋楼)、选项(要跑哪些规则的配置)、回调函数。调用时,axe-core 会把一个 JavaScript 运行脚本注入到页面里,在浏览器环境中遍历 DOM 树,把所有元素作为候选对象交给规则引擎。
规则引擎是它的“大脑”。每一条规则都由两部分组成:一个 CSS 选择器匹配器(决定这个规则关心哪些元素,比如 img 标签)和一个 JavaScript 检查函数(对匹配到的元素做布尔判定)。当一个元素同时满足“被规则关心”且“检查不通过”时,它就被记录为一个 violations 成员。整个扫描是异步的,返回一个 Promise,结果中包含违规、通过、无法自动判断三类信息。
小斧最让物业团队省心的一点,是它连“房间里的密室”也能进——现代前端组件常把样式封装在影子 DOM 里,就像大厦楼内的隐形隔间。axe-core 对此有专门支持,让阴影中的违规也无所遁形。
- n(页面元素)DOM 树上的任意一个节点,可能是
<button>、<img>或<a>标签。小斧的眼睛会逐个扫过它们。 - ∈ DOM“属于页面文档树”,意思是只在当前网页的范围内巡逻。SPA 切换路由后,DOM 变了,需要重新跑一次。
- rule(n) = false某条规则对这个元素“否决”了。好比检查清单上写着“按钮必须有名字”,而这个按钮没有名字,于是被判定违规。
规则体系解析
小斧手里的检查清单不是随手写的,而是有官方依据的“标准手册”。axe-core 内置 90 多条规则,每一条都标注了对应的 WCAG 等级标签:wcag2a、wcag2aa、wcag2aaa,分别对应 A 级、AA 级、AAA 级;还有一类不计入 WCAG 但很实用的规则,叫 best-practice,比如“页面应包含 landmark 区域”。
用 runOnly 可以筛选规则集——比如只跑 wcag2a 和 wcag2aa,满足大多数公司合规验收的要求。每条规则还有自己的 impact 字段,相当于小斧给问题贴的标签颜色:critical(红色,完全不可用)、serious(橙色,严重影响操作)、moderate(黄色,造成困扰)、minor(蓝色,轻微瑕疵)。
这套“颜色标签”体系让团队沟通变得简单:产品经理不需要读懂代码,只需要知道“这周必须把红色和橙色清完”。
- R(规则集合)本次扫描被触发的所有规则的集合。比如页面里有图,就会触发
image-alt。 - →(映射)每条规则都对应且只对应一个严重级别。就像小斧对每条违规只会贴一种颜色的标签。
- {critical, serious, moderate, minor}四个档位从重到轻。critical 意味着某类用户完全无法完成核心任务。
常见检测项
巡检半年后,小斧带回了一份高频问题排行。排在榜首的是三类:颜色对比度不足、图片缺替代文本、按钮没有名字。它们占了全部违规的六成以上,而且修复成本极低。
颜色对比度检测是最有意思的一条。它不只是比较“哪个颜色亮一点”,而是计算两种颜色在真实屏幕上的感知亮度差。WCAG AA 要求普通文本的对比度至少达到 4.5:1,大号文本可以放宽到 3:1。很多设计师选了一个浅灰配白底,肉眼勉强能看,但视力不佳的用户就彻底看不清了。
图片替代文本和按钮名称则是“可访问名称”问题。屏幕阅读器用户看不到图片,全靠 alt 文本描述图片内容;一个只有图标没有文字的按钮,对读屏用户来说就是“无法理解的空白按钮”。axe-core 的 image-alt 和 button-name 规则专门抓这两类问题。
小斧把这三大类问题抓出来之后,真正动手修其实很快:给图片补一句 alt、给按钮加 aria-label、调整调色板里的两三个色值——大厦的“小毛病”大多就是这样十来分钟能解决的事。
- L₁(较亮色的相对亮度)先分别计算前景色和背景色的感知亮度,亮的那个记为 L₁。白色约等于 1,黑色约等于 0。
- L₂(较暗色的相对亮度)亮度较低的那个颜色。如果背景是
#818CF8,前景是#FFFFFF,就要分别算出两者的亮度值。 - + 0.05修正系数,避免纯黑亮度为 0 导致分母为 0 的情况,让公式始终可算。
- 比值 ≥ 4.5WCAG AA 对常规字号文本的要求。这个阈值保证了低视力用户能舒服地阅读。
集成工作流
小斧再能干,如果只是每个月手工抱着它爬一次楼,那就少了一半价值。聪明的物业团队会把它的巡检排进每天的晨间例行程序。前端领域也完全一样:把 axe-core 嵌进开发链路,让它在代码提交、集成测试、CI 构建的每个关键节点都自动跑一遍。
最轻量的方式是用官方命令行工具。在项目里安装 @axe-core/cli 后,一条命令就能扫描指定 URL 或 HTML 文件:
--exit 表示发现违规就以非零退出码结束进程,这正是 CI 门禁的基础。更推荐的做法是把它集成到测试框架里:Jest 生态常用 jest-axe,配合 expect(page).toHaveNoViolations() 一条断言;Playwright 和 Cypress 也都有对应的 axe 插件,可以在页面跳转、用户交互之后实时扫描动态内容。
流水线的设计遵循“关键路径优先”原则——不是每张子页面都要全量跑,而是把登录、支付、内容阅读等用户旅程的核心路由列为必检项。CI 阶段一旦发现 critical 或 serious 级违规,直接让构建失败,挡住合并请求。
当小斧的巡检被编进每日晨巡,物业团队再也不用担心“上周漏检的盲道这周有没有人修好”——因为每天早上它都会坚定地重新报告一遍。
- ∪(并集)把红色问题清单和橙色问题清单合并成一张总表。物业团队最关心这两类。
- = ∅(空集)合并之后一张标签都没有,说明高优先级问题为零,大厦可以继续营业。
- ⇔(当且仅当)左边成立右边必成立,右边成立左边也必成立。也就是“门禁是否通过”和“高危及严重违规是否清零”完全等价。
npx @axe-core/cli http://localhost:3000 --exit报告与修复
小斧交回的巡检报告,是一份结构严谨的 JSON 文档。读懂它,是修复工作的第一步。报告的顶级字段有三个:violations(违规项)、passes(通过项)、incomplete(无法自动判断项)。很多人只看 violations,但 incomplete 同样重要——它相当于是小斧在报告里写的“这块我判断不了,请人工来确认一下”。
每个 violation 对象里包含若干个 nodes,每个 node 指明了具体的 DOM 目标、影响描述和修复建议。有了这些坐标,开发人员不需要费劲猜测“到底哪张图缺 alt 文本”,直接定位即可。
修复顺序不应该平均用力。推荐的做法是给每个违规计算一个优先级:严重级别越高的权重越大,影响的节点数越多越优先。先把红色和橙色清零,再处理黄色,最后有精力时优化蓝色。修复完成一轮后,把小斧重新拉出来跑一遍,确认问题不再复发——这就是“复检闭环”。
小斧把一栋楼的问题整理成了可执行的工单队列。物业团队只要照着优先级列表逐项销号,复检后把报告贴进团队文档,整个项目的无障碍水位就能稳步上升。
- weight(impact)(严重级别权重)critical=4、serious=3、moderate=2、minor=1。红色问题权重最高,必须第一个处理。
- count(nodes)(受影响节点数)报告里这条规则命中多少个页面元素。影响面越广,越该优先。
- ×(相乘)两个维度综合打分。一条影响 5 个节点的 serious(3×5=15)会比影响 8 个节点的 moderate(2×8=16)直觉上排在后面,这正是量化排序的好处。
AI 应用场景
CI/CD 无障碍质量门禁
阶段:持续集成与质量保障
解决的问题:传统无障碍检查靠人工抽检,版本迭代后新页面经常漏检;人工复测成本高、标准不统一。axe-core 接入 CI 后,每次提交代码都会自动扫描关键页面,critical 和 serious 级问题直接让构建失败,把问题拦截在合并之前。
具体案例:某电商团队在 GitHub Actions 中配置了 axe 扫描步骤,对登录、购物车、结算三个核心路由跑 wcag2a 和 wcag2aa 规则。一次改版中,按钮的 hover 样式对比度降到 3.2:1,流水线立刻变红,开发者在合并前就完成了修复。
组件库无障碍守护
阶段:开发环境实时反馈
解决的问题:设计系统中的每个组件都可能被复用数百次,组件级别的一个无障碍缺陷会被放大到整个产品线。传统方式是在组件发布后靠用户投诉才发现问题,成本很高。
具体案例:某公司的 Design System 团队在 Storybook 中接入 axe-playwright,给每个组件 story 自动跑无障碍检测。新提交的“下拉选择器”被检测出无法用键盘展开,提交被阻塞,组件库的每个组件从出生起就自带无障碍体检报告。
无障碍报告智能解读
阶段:报告生成与辅助决策
解决的问题:axe-core 输出的 JSON 报告包含大量技术字段,非无障碍方向的开发者阅读门槛高。结合 AI 工具把报告翻译成通俗的自然语言摘要,可大幅降低团队的学习成本。
具体案例:测试团队将 axe 扫描结果输入大模型,生成“这轮迭代新增了 3 个色觉障碍高风险点,分布在首页促销横幅”这样的摘要,同时附上修复代码片段建议,产品经理和技术负责人一眼就能看懂风险。
Python 演示
# -*- coding: utf-8 -*-"""axe-core 违规报告解读器:提取并按优先级排序无障碍问题。"""import jsonimport sys SEVERITY_WEIGHT = { "critical": 4, "serious": 3, "moderate": 2, "minor": 1,} DEMO_REPORT = { "violations": [ { "id": "color-contrast", "impact": "serious", "help": "确保文本与背景对比度至少 4.5:1", "nodes": [{"target": ".btn-primary"}, {"target": ".nav-link"}], }, { "id": "image-alt", "impact": "critical", "help": "为信息型图片提供 alt 替代文本", "nodes": [{"target": "img.hero"}], }, { "id": "button-name", "impact": "serious", "help": "按钮元素需要可访问名称", "nodes": [{"target": "button.icon-close"}], }, { "id": "region", "impact": "moderate", "help": "页面需要 landmark 区域便于读屏导航", "nodes": [{"target": "html"}], }, ]} def load_report(path): """读取 axe-core 输出的 JSON 报告;未传路径时使用演示数据。""" if path is None: return DEMO_REPORT with open(path, "r", encoding="utf-8") as f: return json.load(f) def summarize(report): """按 impact 权重降序汇总 violations。""" items = [] for violation in report.get("violations", []): items.append({ "rule": violation["id"], "impact": violation.get("impact", "minor"), "count": len(violation.get("nodes", [])), "help": violation.get("help", ""), }) items.sort(key=lambda x: SEVERITY_WEIGHT.get(x["impact"], 0), reverse=True) return items def render(items, top_n=5): """打印修复优先级清单。""" print("===== axe-core 修复优先级清单 =====") for idx, item in enumerate(items[:top_n], 1): print("{0}. [{1}] 影响 {2} 个节点: {3}".format( idx, item["impact"], item["count"], item["rule"])) print(" 建议: " + item["help"]) def main(): path = sys.argv[1] if len(sys.argv) > 1 else None report = load_report(path) items = summarize(report) render(items) if __name__ == "__main__": main()
SEVERITY_WEIGHT = {...}为四个 impact 档位分配权重分,critical 最高 4 分,这是排序的依据。def load_report(path):兼容两种数据源:传入文件路径读真实报告,否则使用内置演示报告。report.get("violations", [])安全取值:报告里没有这个键时返回空列表,避免 KeyError。len(violation.get("nodes", []))统计每条规则影响了多少个节点,用于衡量影响范围。items.sort(key=lambda x: SEVERITY_WEIGHT.get(...))按权重降序排列,权重最高的红色问题排最前面。if __name__ == "__main__":保证脚本既能作为模块被导入,也能被命令行直接执行。
常见误区
误区既然 axe-core 跑了没有报错,我们的网站应该完全无障碍了吧?评论区都说这个工具很权威,应该可以放心上线了。
正解axe-core 只能自动覆盖约 30%-40% 的 WCAG 检查点。它擅长检测“结构性、可判定”的问题(缺 alt、对比度不足),但无法判断键盘焦点顺序是否合理、屏幕阅读器的实际朗读体验是否顺畅。自动化扫描通过,只代表机器的检查清单过了,人工测试环节仍然不可省略。
为何常见:工具给出的“绿色通过”给了人很强的确定感,加上很多团队没有专职无障碍测试人员,容易把机器报告当作唯一权威。
误区这个灰色文字和白色背景看着挺清楚的,比旁边那个红色差点,但应该到不了 4.5 也差不多 4 了吧?设计师看着明显就行。
正解对比度不是“看起来像不像”,而是基于颜色相对亮度 L 的精确公式计算。公式里还要考虑透明度混合后的实际渲染颜色。看着清楚的浅灰色在公式里可能只有 2.8:1,远不达标,特别是在强光下或视力不佳场景。
为何常见:人对颜色的感知是相对的、主观的,尤其在明亮屏幕上会对低对比度产生“还行”的错觉。
误区我写了个自动化脚本,打开首页跑一遍 axe-core,结果全绿,那这个应用无障碍没问题。后面路由跳转、弹窗打开我看也差不多。
正解现代 SPA 是动态的——路由切换会重新挂载 DOM,弹窗、表单校验、无限滚动都会产生新的内容。axe 只在“当前可见 DOM”上工作,所以必须在关键交互和路由状态上多次扫描,才能覆盖用户真实走过的路径。
为何常见:习惯性地把网页理解成静态 HTML 文档,忽略了前端框架带来的运行时 DOM 变化。
误区报告里 passes 列出了 40 多条规则,说明这些规则我们都完美通过了,可以卸载了这个环节了。
正解passes 只表示“本次扫描中,这条规则没有发现违规”——它可能是真的合规,也可能是页面上没有该规则关心的元素(比如页面没有视频,video-caption 规则就无事可做)。更需要注意的是 incomplete,它代表工具无法自动判断,需要人工确认。
为何常见:报告的字段名给了人“已通过”的直觉,忽略了 passes 的底层含义是“没抓到问题”而非“确认没有问题”。
误区A11y 修复嘛,就是一个一个给 img 补 alt 文本。把违规列表里 alt 相关的清完,剩下视觉上的问题设计师调调色就行了。
正解alt 只是众多规则中的一条。按钮的可访问名称、ARIA 属性的正确用法、表单标签的关联、语义化地标区域、键盘可达性,都是 axe-core 的检测范围,更不用说它管不到的人工测试项。把 A11y 等同于加 alt,就像把大楼安全等同于检查灭火器。
为何常见:网上关于无障碍的入门文章大多拿“给图片加 alt”做例子,导致不少开发者形成“这就是全部”的刻板印象。
面试题
Q1. axe-core 的核心功能是什么?
- A.网页性能优化工具
- B.浏览器兼容性检测库
- C.前端代码语法检查器
- D.前端无障碍自动化检测引擎← 正确答案
答案:D。axe-core 是 Deque Systems 开源的 A11y 检测引擎,扫描 DOM 后输出违规报告。它不做性能分析(排除 A)、不做浏览器兼容判断(排除 B)、也不是语法检查器(排除 C)。
Q2. WCAG 2.1 AA 级对常规文本对比度的最低要求是多少?
- A.3:1
- B.4.5:1← 正确答案
- C.7:1
- D.2:1
答案:B。WCAG AA 要求常规文本对比度不低于 4.5:1。3:1 是大号文本的阈值,7:1 是 AAA 级要求,2:1 不满足任何 WCAG 文本标准。
Q3. axe-core 报告的 violations 数组中,nodes 字段存放的是什么?
- A.违反规则的 DOM 节点← 正确答案
- B.规则的完整源码
- C.规则匹配成功的节点数
- D.页面的加载时间
答案:A。nodes 列出具体违规的页面元素及其 target 定位和影响描述,帮助开发者直接定位修复,而非规则源码(排除 B)、统计数量(排除 C)或性能数据(排除 D)。
Q4. 在 CI/CD 流水线中集成 axe-core 时,最合理的做法是?
- A.仅在本地开发环境手动运行
- B.在 CI 中扫描但失败不阻塞
- C.在关键路由和组件测试里自动扫描← 正确答案
- D.只在发布前由 QA 手动跑一次
答案:C。正确做法是在核心用户旅程和组件测试中自动扫描,并用失败门禁阻塞合并。本地手跑(A)和 QA 手动(D)无法持续保障;扫描失败不阻塞(B)会让门禁形同虚设。
Q5. axe-core 中 color-contrast 规则的默认 impact 通常属于哪一级?
- A.critical
- B.serious← 正确答案
- C.moderate
- D.minor
答案:B。color-contrast 默认标记为 serious,因为低对比度严重影响阅读能力但页面仍可操作。critical 一般留给“完全无法完成任务”的问题,例如无名称的按钮被读屏完全忽略。
Q6. 关于 axe 报告中的 incomplete 状态,正确的理解是?
- A.表示检测失败需要重跑
- B.表示可能存在问题需人工确认← 正确答案
- C.表示页面有暗影 DOM 无法扫描
- D.表示该规则严重级别过低被忽略
答案:B。incomplete 表示自动化工具无法做出确定判断,需要人工介入确认。它不是执行错误(排除 A),也不仅是 shadow DOM 的问题(排除 C),更不是被忽略(排除 D)。
Q7. 在 axe-core 的 run 配置中,通过什么参数可以只执行指定规则的检测?
- A.runOnly← 正确答案
- B.excludeRules
- C.skip
- D.ignoreTags
答案:A。runOnly 接受 { type: 'rule', values: [...] } 或 tag 列表来限定执行范围。excludeRules、skip、ignoreTags 都不是 axe-core 的合法 API,属于干扰项。
Q8. jest-axe 的 toHaveNoViolations 断言失败时,错误信息主要来自报告中的哪个字段?
- A.passes 数组的条目数
- B.violations 数组的元素← 正确答案
- C.incomplete 数组的内容
- D.url 字段的赋值
答案:B。断言的作用是“不允许存在任何违反项”,一旦发现 violations 有内容就抛出失败。passes(A)和 incomplete(C)都不触发失败,url(D)只是报告元信息。
Q9. 下列哪些是无障碍检测中需要人工辅助判断的问题?
- A.键盘焦点顺序的合理性← 正确答案
- B.图片标签缺少 alt 属性
- C.屏幕阅读器朗读的实际语义← 正确答案
- D.文字颜色对比度数值不达标
答案:AC。焦点顺序和朗读语义依赖真实使用场景中的交互体验,自动化工具很难判定。缺少 alt(B)和对比度不足(D)都是 axe-core 能稳定自动捕获的结构性问题。
Q10. 关于 axe-core 在动态渲染页面(SPA)中的使用,正确的说法是?
- A.页面加载完成后只静态扫描一次即可
- B.路由切换后需重新扫描新挂载的内容← 正确答案
- C.异步数据渲染结束后再触发扫描更准确← 正确答案
- D.shadow DOM 内部元素无法被 axe-core 检测
答案:BC。SPA 的路由切换会重建 DOM,必须在关键状态重新扫描(B);异步渲染需等待数据挂载完成后再扫,否则会漏检(C)。只扫一次(A)会错过动态内容;shadow DOM 内元素 axe-core 是可以检测的,D 错误。