vivo 场景下的 H5 无障碍适配实践
作者:vivo 互联网前端团队- Zhang Li、Dai Wenkuan
随着信息无障碍的建设越来越受重视,开发人员在无障碍适配中也遇到了越来越多的挑战。本文是笔者在 vivo 开发 H5 项目做无障碍适配的实践总结。本文主要介绍了在前端项目中常用的无障碍手势和无障碍属性,并且结合具体的开发案例为开发者真实展示了适配要点,提供组件适配思路。希望本文能为前端开发者带来更多的参考和帮助。
一、背景
1.1 无障碍适配认知
无障碍(Accessibility)是指为各种能力水平的人们提供公平和平等的机会和体验,以便他们可以更容易地访问、使用和参与社会中的各种产品、服务和环境。这些人包括身体残疾、听力、视觉、认知和学习障碍等各种能力水平的人。
1.2 无障碍适配原因
常见的障碍类型包括:视觉障碍、听觉障碍、认知障碍、行动障碍。这些“有障碍”的群体,在使用软件时无法像普通人一样操作,他们或许看不清,需要更大的字体,或许看不到,需要语音播报,又或许听不清听不到,需要依赖视觉反馈,或许肢体操作不方便,无法自由操纵手机。
所以,国家和企业需要站在“有障碍”的群体思考,为各种能力水平的人们提供公平和平等的机会和体验。
【政策】:2011 年《中国残疾人事业“十二五”规划纲要》指出了, 建设无障碍环境的主要任务之 一就是加强信息无障碍建设,并明确了相关的政策措施。
【包容性】:无障碍适配是一个重要的设计和开发目标,可以帮助您的网站或应用程序更加包容和可访问。
【经济性】:无障碍适配不仅是一种道德义务,也是一种经济利益,因为它可以为您的网站带来更广泛的受众和更好的用户体验,带动业务增长。
1.3 无障碍适配引导
除了系统默认支持的大字体、内容播报、语音识别、字幕、语音控制等基础无障碍适配,更多的页面交互还是需要前端工程师们完成适配,所以开发者们需要开发出可识别的 web 页面,让障碍群体可以正常访问和使用。本文主要针对视障人群进行无障碍适配。
二、无障碍操作
在无障碍开发前,前端工程师们需要了解下无障碍的操作手势,站在障碍人群的角度体验操作,这样才能开发出更好的交互体验。也能避免因操作错误导致开发错误和重复开发。
2.1 读屏幕软件
首先我们需要了解下针对视力障碍人员使用的读屏设备或软件,常见的读屏软件,如下列表:
由于我们是在 vivo 手机内做移动端安卓手机无障碍适配,以下的方案、案例等介绍均是基于 Android-TalkBack。
2.2 常用操作手势
借助 TalkBack 手势,可以在 Android 设备上进行导航和执行常用操作,以下操作为安卓 9.1 版及更高版本,仅在 vivo 手机的操作。官方链接:Talkback 手势。
三、常用属性介绍
3.1 aria 属性
ARIA 全称“Accessible Rich Internet Applications(可访问的富互联网应用)”,是 W3C 的 Web 无障碍推进组织在 2014 年 3 月 20 日发布的可访问富互联网应用实现指南。是一个为残疾人士等提供无障碍访问动态、可交互 Web 内容的技术规范。ARIA 用于提高使用 HTML、CSS、JavaScript、AJAX 和相关技术开发的动态内容和高级 UI 控件的辅助功能。ARIA 现在正式成为 HTML5 规范的一部分,还可以嵌入在常用的 JavaScript 库中。
3.2 aria 状态
3.3 role 属性
role 将元素标记为不同的属性,常用属性“button(按钮),region(图形)”,根据定义的不同属性,播报不同的内容。
3.4 tabindex 属性
tabindex 属性的使用可以使得原本无法取得焦点的元素获取焦点。目的是为了使用户可以用键盘访问任何可以用鼠标访问的元素。我们知道,使用 Tab 键可以按文档顺序 tab 到所有可以获取焦点的元素。tabindex 可以设置为 -1, 0 或者是任何自然数。
当用户使用 Tab 键浏览页面时,元素获取焦点的顺序是按照 HTML 代码里面元素出现的顺序排列的,有时跟实际看到的页面顺序并不一致。
四、无障碍适配案例
4.1 项目适配案例
1.语言设置
在全局<html>元素中的 lang 属性设置页面的语言:
英文:
中文:
国内的中文项目一般需要设置为中文,如果设置成了英文,有些数字会播报成英文。
2.层级属性
如果项目还没开始适配,则开启无障碍播报,会发现标签 div 和 span 默认作为焦点并播报,这样的话焦点和播报内容就非常分散。
如下图 2,就会有 3 个焦点,这样过于分散,一般适配会将 ui 的模块作为一个大焦点整体播报,此时在外层 div 添加 tabindex=1 聚焦。
图 1:原图
图 2:分散焦点
图 3:一个大焦点
备注:绿色框为焦点标记
(1)聚焦播报
因为 div 标签会自动播报,所以就算焦点聚焦到外层,但是内层还是会自动播报。
播报内容:100 元话费充值。
(2)自定义播报
但是会存在 div 里写的内容和需要播报的内容不一致,则可在外层 tabindex="1"聚焦后,通过 aria-label 写上自定义内容
播报内容:获得 100 元话费充值券
3.聚焦样式
聚焦可通过 tabindex 实现,但是聚焦后样式会有黄色的边框,可通过 outline: none 去除,但是页面中的焦点太多,可通过全局去除。
在公共 css 文件里设置:
4.图片播报
(1)图片的 alt 属性设置
图片通过 img 标签实现,如果图片不可聚焦,则设置 alt=""即可。如果可聚焦并需要播报内容,建议通过 aria-label 设置。如果使用 alt,一旦图片加载不出来,就会把 alt 的内容显示出来,而且 alt 内容没有样式,在 H5 页面上会显得很突兀。
(2)去除图片默认属性(图形)播报
可在 img 标签里,将 role 属性改为 row
5.按钮播报
通常 ui 上的按钮在选中后,需要播报按钮内容+按钮+引导操作(如:点按两次即可激活)
首先需要聚焦(tabindex="1")到节点,然后需要在按钮上的节点上写上 role="button",加上这个属性后,后面会自动播报“按钮,点按两次即可激活”
案例:div 中的按钮
播报内容:立即使用,按钮,点按两次即可激活
案例:img 中的按钮
播报内容:开,按钮,点按两次即可激活
6.数字播报
如:手机号:181****8805 中的星号不能正确播报,而是播报成乘,数字播报成整数。
错误播报:一百八十一乘乘乘乘八八零五
需正确播报:幺八幺星号星号星号星号八八零五
可写一个公共方法映射数字、*号和 X 号播报文案,在需要的转换方法里调用该方法形成手机号播报文案。
公共方法如下:
7.自定义事件播报
目前带有点击事件的节点,聚焦后会默认播报点按两次即可激活(系统规范),但是这个引导不够明确,需要就具体交互场景制定播报内容,如:点按两次即可选中,点按两次即可打开红包等。
方案:在 click 事件节点里层包裹 div,并将焦点 tabindex 写在这一层的 div 上,再自定义播报的内容即可。
播报:100 元话费充值,点按两次即可激活
播报:获得 100 元话费充值券,点按两次即可选中
8.额外内容播报
如:第几项,共几项,点按两次即可选中
方案:可用空 div 加 aria-label 实现
9.整体播报
通常整体播报的内容较多,且播报顺序非代码书写顺序,这个时候就需要在外层焦点里,控制播报的内容,主要可通过两种方法实现,aria-label 拼接参数和 aria-labelledby 设置 id。
需要播报的内容:话费充值券,50 元满减券,满 199 元可使用,立即使用。
(1)aria-label 拼接参数
可通过在外层节点设置 tabindex=1 后,再添加 aria-label 属性按照需要的顺序添加参数
案例:
(2)aria-labelledby 设置 id
在外层节点设置 tabindex=1 后,在需要播报的内容节点里添加 id 值,并将 id 值按照需要的顺序写在外层节点 aria-labelledby 属性里
案例:
10.局部特殊播报
如优惠券模块,可整体选中播报全部优惠券内容,但内部立即使用按钮又可聚焦播报跳转。
方案:外层聚焦-设置 tabindex=1,播报整块内容设置 aria-label 拼接参数或 aria-labelledby 设置 id,内层部分内容聚焦,聚焦内容设置 tabindex=1,不聚焦的部分设置 aria-hidden="true"(不然在选中外层焦点时候,内层非聚焦部分会重复播报)。
11. 解决组件设置 aria-labelledby="[id]”后只重复播报第一条数据的内容
因组件被 Vue 中的模板 for 循环调用,每个内容不一样,用同一个 id 会导致播报同一个内容,且没有 key 值的区分,需要解决设置 aria-labelledby="[id]”后只重复播报第一条数据的内容
此时需要根据唯一标识区分 id:可在 id 后拼接唯一标识号,如:"`amount-${item.id}`"
案例:
4.2 组件适配案例
除了具体业务的适配,还有一些共性的组件问题需要组件库统一适配,这样能减少各业务单独适配的工作量。
任何一个无障碍组件的适配,都包含播报内容管理、焦点管理两部分。对于播报内容管理,几乎所有的组件适配都会涉及到。无障碍 aria role、states 一般系统都有自己默认的播报行为,尽量保持系统默认的播报。当然,也可以通过 aria-label 定制播报内容。焦点管理主要针对元素会发生变化的组件,如弹窗、轮播图、各类选择器等。
焦点管理的基本方法有 3 种:
tabindex 属性;
aria-hidden 属性;
el.focus()方法。
tabindex="undefined"即意味着元素不可聚焦,为其他值意味着元素可聚焦。aria-hidden 为 true,意味着不可聚焦且不播报。对于可聚焦的元素,可以通过 el.focus()方法直接聚焦在该元素上。
下面通过典型案例来说明各个组件是如何处理焦点和播报内容的。另外,由于一个 html 中可能多次使用同一个组件,所以下面的案例都是在不使用 id 属性的基础上完成适配的。如果一定要在某个组件使用 id 属性,记得通过随机函数对 id 属性做随机命名。
1. switch、checkbox、radio 组件
这几个组件相对简单,使用系统默认的 role 即可。完成播报内容的适配即可,不需要做焦点管理。
以 switch 为例:首先是 role=switch,然后通过 aria-check 播报开关状态,最后通过 aria-disabled 来播报是否禁用。
2.弹窗、对话框组件
弹窗组件是一个相对复杂的组件,既涉及到焦点管理,也涉及到播报内容管理。通过弹窗组件主要介绍焦点管理的小技巧。
该组件正在业务中的使用情况:
我们的弹窗组件相对比较通用,主要包括标题、内容、按钮三个部分。其中,标题、内容既可以通过属性传入,也可以通过 slot 传入。对于 slot 传入的部分,组件不太好控制。在业务使用过程中,还普遍存在只有内容的弹窗。
适配目标:弹窗弹出时需要自动聚焦在标题上并播报。
弹窗焦点顺序:弹窗弹出时需要自动聚焦在标题上并朗读标题,然后手动移动下个焦点聚焦到说明文本,最后聚焦到操作按钮。
(1)焦点管理
首先,需要解决弹窗弹出时的焦点聚焦问题。由于弹窗组件非常灵活,所以使用场景也非常多样。对于属性传入的标题,组件内部可以获取到该元素,并完成聚焦播报;对于 slot 插入的就显得无能为力。
对于 slot 这种情况,组件和业务约定了一个属性,如果业务想聚焦在这个 dom 元素上,就给该元素添加这个属性。组件会通过 el.querySelector 查询弹窗的后代元素,在弹窗弹出时自动添加 tabindex 并完成聚焦播报。
另外,考虑到有业务要求弹出时不自动聚焦在弹窗上,所以还提供了属性,用于关闭焦点管理功能。
然后,需要解决弹窗关闭时的焦点还原问题。在弹窗弹出前保存当前聚焦的元素 document.activeElement,关闭弹窗以后,通过 el.focus()手动聚焦在该元素上。
还剩一个焦点穿透问题。很多安卓系统 aria-modal 属性不起作用,所以焦点还能够穿透弹窗,选中页面上的元素。
目前组件没有特别好的办法处理这点。开发过程中,可以对需要屏蔽的元素添加 aria-hidden=true 属性
(2)播报内容管理
这部分就显得比较简单。对于 slot 的情况,播报内容可以交给业务开发控制;对于属性传入的情况,可以添加组件属性,为业务提供定制化播报的能力。
3.地址选择器组件
地址选择器是一个滚动式交互的组件。在这个组件的开发过程中使用到了一种小技巧,即不聚焦在某元素上,也能自动播报该元素变化的内容。
该组件在业务中的使用情况:业务开发没有对该组件定制化,所以只用考虑组件内部的适配即可。
适配目标:在弹窗弹出时自动聚焦到标题上并播报。下图是焦点的排布。
以该图为例,焦点聚焦在第一列的时候,播报“河北省,滑动滚轮控件,可上下滚动切换”;另外,在每一列滚动结束时,播报当前选中的地址,如“河北省,石家庄市,桥西区”
(1)焦点管理
同弹窗组件。
(2)播报内容管理
这里的难点就在于每一列滚动结束的时候,需要播报变化后的地址,但是此时焦点还在选中的这列上。
这里添加了一个隐藏的元素(不在页面上显示),并添加属性 aria-live="polite",滚动结束的时候修改 ariaPickerContent 的值。如果不想播报则将 ariaPickerContent 置为空字符串,弹窗关闭的时候记得置为空。
五、总结
本文是在 vivo 体系下的无障碍适配实践,主要提炼总结了一些适配方法,对内外部的无障碍适配工作都有一定的参考和借鉴价值。下一步计划丰富和完善组件库的适配,沉淀一套高效的适配方案,尽量减少开发人员的适配成本,提高开发效率。
开发者应秉持着技术有温、代码有爱的态度,坚守“勿以善小而不为”的准则,以用户为导向,让我们的产品和服务照亮每一位用户,让“障碍群体”在这个有爱的互联网时代,紧跟时代潮流。
版权声明: 本文为 InfoQ 作者【vivo互联网技术】的原创文章。
原文链接:【http://xie.infoq.cn/article/687dbd63ff2f26778b5d72896】。文章转载请联系作者。
评论