The power of the Web is in its universality. Access by everyone regardless of disability is an essential aspect," said Tim Berners-Lee, W3C Director and inventor of the World Wide Web.
30年前,Tim Berners-Lee 在欧洲核子研究中心创建了第一个 Web 网页,宣告了万维网的诞生。自此,万维网就承载着开放平等的愿景。
Accessibility——无障碍设计&信息无障碍(也简称为 A11y),虽然常常会被解释为”为残障人士服务“,但其无障碍设计的核心在于为所有人提供同等的体验。我们每个人都有可能在某些时刻成为失能者,这称为场景性残疾(situational disability & temporary disability),比如受伤骨折后,暂时失去了部分活动能力。又比如被强光照射时,看不清楚事物,在嘈杂地铁中的听力产生障碍等等。
根据 W3C 组织的定义,Web accessibility 意味着每个人都可以感知、理解并与 Web 交互,甚至为 Web 做出贡献。中国工信部也指出,信息无障碍是指通过信息化手段弥补身体机能、所处环境等存在的差异,使任何人(无论是健全人还是残疾人,无论是年轻人还是老年人)都能平等、方便、安全地获取、交互、使用信息。
但在万物互联的当下,尽管我们的衣食住行早已与网络世界息息相关,互联网并未成一个平等的,人人都可以访问的世界。根据2022 年 The WebAIM Million 统计报告,在对 100万 个网站首页进行无障碍分析后,得到的结果却差强人意:
-
在 100 万个首页中,一共检测到 50,829,406 项非重复的无障碍错误,平均每个首页有50.8个错误。
-
在残障用户的页面访问流程中,每交互 19 个首页元素,就可能遇到一个无障碍错误
图源:2022 年 The WebAIM Million 报告
在这些页面无障碍错误中,96.5%的错误归属于以下五类:
-
页面颜色对比度不达标,影响视力障碍用户的访问体验。
-
未定义
<img />
标签的alt
属性,影响辅助技术(Assistive technologies, ATs) 如屏幕阅读器等设备获取图片信息。 -
空链接和空按钮,指不包含不包含实际的文本的
<a>
标签或<button>
标签。这些标签只包含一个图像或一个文本的图像,会导致使用 ATs 设备的用户无法感知可交互元素的实际用途。 -
表单元素
<input />
标签没有对应的<label>
标签。</label><label>
标签对于 ATs 设备至关重要。没有它,用户将无法感知他们在与哪个<input />
标签交互。 -
<html>
标签没有设置lang
属性。不同的语言类型在屏幕阅读器中的发音是不同的,比如six
单词在法语和英文两种类型的屏幕阅读器中的发音就非常的不同。在</html><html>
上定义lang
属性,会告知 ATs 设备当前页面所使用的语言。
作为前端开发者,我们要如何把关页面的无障碍功能呢?
在前端开发的视角中,每一个 Web 应用都可以拆解为 HTML、CSS 和 JavaScript。HTML 会经过 HTML Parser 将 HTML 结构转换成 DOM Tree;CSS 会经过 CSS Parser 将 CSS 转换成 CSSOM Tree。最终,浏览器根据 DOM Tree 和 CSSOM Tree 构建出最终的 Render Tree。
对于无障碍 Web 应用,除了包含 DOM 和 CSSOM 之外,将包含 AOM (Accessibility Tree,可访问性树)。AOM 可访问性树和 DOM 树平行存在。简单来说,可访问性树是 DOM 树的一个子集。每个需要暴露给 ATs 辅助技术的 DOM 元素都对应一个在可访问树中存在的无障碍对象。定义 AOM 实现的标准是 WAI-ARIA(Web Accessibility Initiative – Accessible Rich Internet Application),即可访问的互联网富应用标准,致力于解决应用的可访问性问题,它与HTML5 标准同属于 W3C 组织。Web 应用的 AOM 也并非遥不可及,打开 Chrome 浏览器的 Devtools,我们即可查看页面的 AOM 结构。
在了解了无障碍的基本概念后,我们分别从 HTML、开发框架以及 CSS等角度,一起来看看无障碍页面的实现方式吧。
编写 HTML 时需要考虑的 Web Accessibility
就像浏览器引擎依赖 HTML 结构以构建页面 UI 骨架,ATs 设备也依赖 HTML 结构来构建页面的 AOM 可访问性树。所以语义化的 HTML 对于实现 Web 应用无障碍至关重要,因为原生的 HTML 标签中包含了构建 AOM 的必要元数据。
参考上图,ATs 设备完全可以正确地渲染滑动输入框,即便我们没有在HTML 标签上添加 WAI-ARIA 属性。但我们在开发时往往会忽略 HTML 元素的实际语意,而更多采用无语意的 <div>
和 <span>
标签 (<div>
和 <span>
之外的近 104 个 HTML 标签都具有语义信息)。因为这两个标签没有默认样式,足够简单,就像白纸一样可以随意画上 CSS 样式。但这样的标签,对于 ATs 设备来说,就是灾难。
以上图为例,对于 <button> FOO </button>
标签,读屏软件将读出 “Button, foo",告知用户当前元素是按钮,包含文字 foo。但对于 <div class="button"> FOO </div>
标签,读屏软件只能读出 “foo”,并不能提示当前元素是一个可交互的按钮。虽然我们也可以通过设置 WAI-ARIA 属性为 HTML 标签增添无障碍语意,比如 <div class="button" role="button"> FOO </div>
,但这样会平添许多额外的工作,也增加了出错的机率:根据 The WebAIM Million 统计报告,包含 ARIA 的页面比不使用 ARIA 的页面,检测出无障碍性错误的可能高 70%。
通过 HTML 提升页面可访问性
规则 1:结构和样式分离
在社区中一直都有人在提倡 CSS裸奔日(CSS Naked Day),编写 HTML 时不要基于 UI 视觉效果(CSS 样式),而是基于 UI 的页面结构,可以确保 HTML 的语义完善,增强页面可访问性。
相关浏览器插件:HeadingsMap - Chrome Web Store
规则 2:只在必要时使用 ARIA
WAI-ARIA 的全称是 Accessible Rich Internet Applications,简称 ARIA,是 W3C规范之一。ARIA 允许 Web 开发者创建只有 ATs 技术(比如屏幕阅读器)可以看到的内容(属性),用以实现 HTML 无法达成的无障碍功能,比如:
-
增强交互式控件的可访问性,比如下拉菜单、弹窗,滑块等
-
为页面结构定义有用的地标
-
定义动态更新的“活动区域”
-
改善键盘可访问性和交互性
ARIA 表现为 HTML 的属性,确定了元素的 ARIA 角色、状态和属性。这些信息帮助 ATs 技术更好地理解 Web 页面,确保用户与页面元素的交互。一般情况下,ARIA 不会影响 Web 页面的渲染,也不会影响鼠标或键盘用户的行为,只有使用辅助技术的用户才能感知到 ARIA。开发人员随意使用 ARIA 所导致的问题,对于页面无障碍功能往往是致命的,而且难以察觉。
但 ARIA 永远无法替代语义化 HTML 标签,NO ARIA is better than bad ARIA。请优先考虑语义最贴近的 HTML 标签,只在必要时使用 ARIA。
相关浏览器插件:
规则 3:提升表单结构的包容性
-
采用
<fieldset>
为表单项分类
当表单分为不同板块时,我们可能会使用<div>
元素实现表单项的样式板块划分,但这样的划分并不利于无障碍设备获得表单项信息,可以使用<fileset>
进行替换。 -
正确使用
label
,为<input />
标签设置对应的label
在实现表单时,我们往往会通过
placeholder
来提示当前表单项的填写内容。这样的设计会导致当input
得到焦点时,placeholder
自动消失,造成用户无法感知当前表单项的内容。我们可以使用label
充当placeholder
,这样的交互方式也称为 Float Label Pattern -
尽可能使用原生的表单元素
在制作表单组件时,我们往往会出于实现 UI 样式的要求,采用<div>
替代原生的表单元素。尽管这些表单组件在视觉和功能上满足了 UI 要求,但它们并未实现原生表单元素的无障碍功能。 -
required
、minlength
、pattern
等表单的原生校验属性,不但可以满足正常的表单校验需求,也具有更好的无障碍支持
规则 4:注意页面的焦点管理,允许用户仅通过键盘完成交互
很多行动不便的用户依赖键盘操作,靠 Tab 键和方向键等浏览网。因此我们在构建 Web 应用的时候要注意:
-
确保页面所有内容都可以通过键盘访问
-
尽可能地提供键盘快捷键交互
-
避免设计只在鼠标 hover 时才会被激活的元素
一些 HTML 的原生标签具备可聚焦属性,也被称为可聚焦元素。这些原生 HTML 元素,天然存在于页面 Tab 键顺序内,内置了键盘事件处理,可以通过 Tab 键聚焦,并且获得焦点时有可见的焦点指示器(往往是显眼的蓝色框框)。但对于无法聚焦的元素,我们可以设置元素的 tabindexlace
属性,使元素可聚焦。
如果想给当前元素生成快捷键的话,可以给元素设置 accesskey
属性。但使用 accesskey
也需注意以下问题:
-
accesskey
值可能与系统或浏览器快捷键或辅助技术功能相冲突 -
当考虑页面国际化时,某些
accesskey
值可能不会出现在一些键盘上 -
依赖于数值的
accesskey
可能会让具有认知障碍的用户感到困惑,因为数值和触发的功能并没有逻辑联系 -
如果没有告知用户快捷键的存在,那么可以会造成用户误触
相关浏览器插件:
规则 5:定义文档的语言类型
在 <html>
标签元素上设置正确的 lang
属性。如果你的页面没有显式设置当前页面所使用的语言,那么读屏软件将无法选择匹配的语音配置文件和字符集,读屏软件读出的页面内容是乱码。所以,为了确保页面的内容正确,请务必为 </html><html>
元素指定有效的BCP 47语言。
规则 6:为 <img />
添加 alt
属性,明确链接和按钮的信息
往往一张表情包图片就可以抵千言万语,但对于读屏软件来说,读取 <img />
标签的有效信息,只能靠 alt
属性。所以不要忘记为 <img />
标签添加描述性的 alt
属性。如果图片只是为了装饰效果,那么可以考虑将 <img />
标签 替换为 CSS 背景图。
与 <img />
标签类似,读屏软件对于 <a>
和 <button>
标签的信息获取,依赖于标签包裹的文本。使用”阅读更多“,甚至图片作为这类标签的包裹内容,并不能为用户提供足够的信息。如果不方便添加文本信息,也可以利用 aria-label
增强元素的语义信息:
<a href="post.php?post=632" aria-label="More on Using Meaningful Link Text">More...</a>
使用前端框架需要考虑的 Web Accessibility
根据 2022 年 The WebAIM Million 统计报告,使用 JavaScript 框架的页面比不使用框架的页面存在更多的无障碍错误,其中 React 开发的页面平均存在 50.8 个错误,Vue 开发的页面存在 63.4 个错误。虽然统计结果不能说明框架导致了这些错误,但在使用框架进行 Web 开发时,常常会忽略使用 HTML 原生标签,或者引入无障碍功能支持性不佳的组件库,导致框架开发的 Web 应用可访问性普遍较差。
提升前端框架的无障碍支持性
规则 1:使用语义化 HTML 标签,完善 HTML 标签的属性
规则 2:在设计组件时考虑整体的 HTML 结构
维护层级明晰的 HTML 结构,对于 Web 应用的无障碍功能十分重要。因为 ATs 软件,特别是读屏软件,不止是由上至下地展现页面信息,更会基于页面不同级别的标题或者文档地标元素进行页面导航。在将页面拆分成不同组件后,保持 HTML 文档结构层级会更加复杂。比如当一个组件包含 <h2>
标签时,可能在一些位置该组件会破坏原有 HTML 文档结构。
规则 3:避免使用无意义的 HTML 标签
在使用 React、Vue 等框架时,我们往往需要将组件包裹在一个根元素中:
但这样的处理在编译后,会在造成元素结构的混乱:
**
<div>
标签混在 <tr>
标签中,会导致读屏软件无法正确解析 table
,造成用户无法访问表格内容。此时,我们应该使用 React 的 <fragment>
标签(Vue 中可以使用 vue-fragment),确保根元素不存在于最终的 DOM 结构内:
规则 4:考虑组件的无障碍性能
在基于框架开发时,我们往往会抽取出功能和交互相对独立的组件,或者直接引用第三方组件库。复杂的交互逻辑并不意味着难以访问,如果每个交互组件都有正确的 ARIA 属性,那么复杂组件也可以实现无障碍交互。
ARIA 5.3.2 Widget Roles定义了常见页面 UI 组件角色,实现了这些组件在 ATs 设备中的交互。根据 Widget Roles
所描述,复杂组件可以由多个独立的小组件构成,而具有复合组件角色的元素可以管理其内部组件。
独立组件角色包括:button、checkbox、gridcell、link、menuitem、menuitemcheckbox、menuitemradio、option、progressbar、radio、scrollbar、searchbox、separator、slider、spinbutton、switch、tab、tabpanel、textbox 和 treeitem。
复合组件角色包括:combobox、grid、listbox、menu、menubar、radiogroup、tablist、tree 和treegrid。
如果我们要实现一个无障碍下拉框组件,这个组件需要被赋予 combobox
角色,而构成它的组件也需要被赋予对应的 ARIR role :
如果你不清楚开发的组件是否满足无障碍需求,可以先看看 Basic Custom Control Requirements。 但很多时候,ARIA 的使用规则也很复杂,需要考虑组件的不同状态,以及属性间的绑定关系,比如 aria-labelledby和aria-describedby 就需要和元素的ID值相绑定在一起。我们可以自己手撸代码,比如管理 ARIA 的 React 钩子函数,也可以引入相关的工具库:React Aria 或者 Vue A11y Utils。
如果使用的是第三方组件库,请尽量选择无障碍支持性较好的组件库,否则手动修复组件库的无障碍错误,将会是一个超级超级庞大的工程。
图为采用 Wave 插件对 element ui 的下拉菜单组件页面进行打分
在无障碍方面做的比较好的库有:
以及一些工具库:
-
GitHub - vue-a11y/vue-accessible-multiselect: Vue.js accessible multiselect component
-
GitHub - vue-a11y/vue-focus-loop: Vue component that helps you to to trap focus in an element.
如果想了解更多框架和无障碍的内容,下列链接或许可以带给你更多启发:
-
GitHub - vue-a11y/awesome-a11y-vue: A list of awesome things related to accessibility in Vue.js
-
Focus management with Vue refs - Learn web development | MDN
编写 CSS 时需要考虑的 Web Accessibility
规则 1:样式与结构分离,视觉效果并不可靠
在使用 CSS 改变页面布局,或者通过 CSS 隐藏元素时,我们也要考虑 ATs 设备对于页面的解析并不基于 CSS,页面的视觉效果并不可靠。
使用 flex、grid以及 float 等布局方式时,虽然页面最终的视觉效果符合 UI 设计布局,但也可能导致页面 HTML 结构与渲染后的视觉布局或者显示顺序不匹配。比如,Flex 布局中的 order
、flex-direction
、flex-auto-flow
等属性,会造成 DOM 顺序和可视内容间的脱节,导致Tab键顺序与页面内容不相符,造成键盘用户的困扰。
对于依赖读屏软件的用户,一些通过 CSS 属性隐藏的元素,实际存在于 AOM Tree 中,用户依然可以感知隐藏内容。如果不希望读屏软件感知元素,并且该元素不可聚焦时,可以使用 aria-hidden="true"
将该元素在 AOM Tree 中隐藏。点击查看更多读屏软件的隐藏方案。
规则 2:保证颜色的对比度,不要将颜色作为唯一的重要信息提示
在页面设计中,我们常常会通过颜色来传达信息。但对于色盲、色弱或者视力障碍用户,他们可能无法区别颜色间的差异,最终导致他们无法理解这些依赖颜色提示的重要信息。比如在上图中通过红色来标注填写错误的表单项,对于色盲用户来说,输入错误项和正确项的提示是相同的。为了保证信息提示的可理解性,可以采用 * 号或者 icon 等方式提示表单项。
同时,我们在开发过程中需要确保颜色对比度符合 WCAG 标准。可以通过 WebAIM 对比度检测网页甚至 Chrome 浏览器开发者工具进行渲染后页面的色彩对比度检测。
一些相关工具:
-
Contrast-ratio:检测两个色值间的对比度
-
who can use:可以告诉你当前用色不适用于视觉障碍用户
-
VisBug - Chrome Web Store:可以对比页面元素间的对比度
-
ChromeLens - Chrome Web Store:模拟不同视觉障碍用户观看页面的效果
规则 3:增添聚焦样式
对于使用键盘操作的用户而言,元素的焦点样式十分重要,WCAG 2.1 SC 1.4.11 Non-Text Contrast 要求视觉焦点指示至少在 3 到 1。如果 UI 设计中没有包含元素聚焦状态的效果,也[不要使用 outline: none
](https://www.outlinenone.com/%5D(https://www.outlinenone.com/)删除默认的聚焦样式!
任何 DOM 元素都具有上图中的状态伪类:
-
:focus
伪类定义了元素聚焦时的样式 -
:focus-within
伪类为父元素添加焦点样式,当父元素的某个后代获得焦点时,匹配对应样式 -
:focus-visible
伪类只在使用键盘聚焦时生效,Firefox 通过:-moz-focusring
支持类似的功能 -
:taget
伪类用于定义元素锚定时的效果
一些其它细节:
-
虽然 Web 应用中的动效丰富了用户体验,但一些动效会引起用户癫痫。所以当用户在设备开启 Reduce Motion 功能时,我们可以通过媒体查询的
prefers-reduced-motion
条件关闭页面动效。在 mac 中设置 Reduce motion
通过
prefers-reduced-motion
关闭页面动效 -
CSS 除了样式盒模型,还有听觉盒模型。通过
speak-as
就可以定义读屏软件在朗诵元素时的方式。可以阅读文章了解更多: Let's Talk About Speech CSS | CSS-Tricks - CSS-Tricks。
Web Accessibility 检测工具清单
一些无障碍检测工具:
浏览器插件:
除了浏览器插件,我们也可以使用命令行检测指定 URL 页面的无障碍指标:
当然,我们也可以在开发时使用检测工具,保证开发代码的无障碍性能:
-
@axe-core/react: 基于 axe-core 对 React 应用进行无障碍性能测试
-
vue-a11y/vue-axe: 基于 axe-core 对 Vue.js 应用进行无障碍性能测试
-
vue-a11y/vue-axe-next: 基于 axe-core 对 Vue.js 3 应用进行无障碍性能测试
-
jsx-eslint/eslint-plugin-jsx-a11y: jsx 无障碍 Lint 工具
-
eslint-plugin-vue-a11y:Vue 无障碍 Lint 工具
-
vue-a11y/eslint-plugin-vuejs-accessibility:对 .vue文件进行静态代码无障碍检测
Please note that only 20% to 50% of all accessibility issues can automatically be detected. Manual testing is always required. For more information see: Web Accessibility Testing, Part 2: Basic Methods and Tools
正如 axe 的免责声明,测试工具只能反映代码层面的无障碍问题,并不能完全保证页面的无障碍交互。不妨抽点时间,在开发结束后闭上眼睛 & 拔掉鼠标 & 遮住触控板真正体验一下产品的无障碍性能吧 ❤️