uni-app 无障碍访问
章节介绍
无障碍访问(Accessibility)是指确保应用能够被所有用户使用,包括那些有视觉、听觉、运动或认知障碍的用户。uni-app 作为一个跨平台开发框架,支持多种无障碍特性,使开发者能够创建对所有用户友好的应用。本章节将详细介绍 uni-app 中的无障碍访问实现方法,包括无障碍属性的使用、屏幕阅读器支持、键盘导航等功能。
核心知识点
1. 无障碍访问基础概念
- 无障碍访问(Accessibility):确保应用能够被所有用户使用,包括残障用户
- 屏幕阅读器(Screen Reader):将屏幕内容转换为语音或盲文的辅助技术
- 键盘导航(Keyboard Navigation):允许用户使用键盘操作应用
- ARIA(Accessible Rich Internet Applications):一组用于增强 Web 内容可访问性的属性
- WCAG(Web Content Accessibility Guidelines):Web 内容无障碍指南,提供了无障碍设计的标准
2. uni-app 中的无障碍支持
uni-app 提供了多种无障碍特性:
- 无障碍属性:通过
aria-*属性增强组件的可访问性 - 屏幕阅读器支持:适配不同平台的屏幕阅读器
- 键盘导航:支持键盘操作和焦点管理
- 语义化标签:使用语义化的组件和标签
3. 无障碍属性的使用
- aria-label:为组件提供替代文本
- aria-labelledby:引用其他元素的文本作为标签
- aria-describedby:提供组件的详细描述
- aria-hidden:控制元素是否对屏幕阅读器可见
- aria-disabled:指示组件是否禁用
- aria-checked:指示复选框或单选按钮的状态
- aria-selected:指示选项是否被选中
- aria-expanded:指示元素是否展开
4. 屏幕阅读器适配
- iOS VoiceOver:苹果设备的屏幕阅读器
- Android TalkBack:安卓设备的屏幕阅读器
- Windows Narrator:Windows 系统的屏幕阅读器
- Web 屏幕阅读器:如 NVDA、JAWS 等
5. 键盘导航实现
- 焦点管理:控制元素的焦点顺序和状态
- 键盘事件:处理键盘按键事件
- 跳过导航:提供跳过重复内容的链接
- 键盘陷阱:避免用户陷入无法退出的键盘导航循环
实用案例分析
案例一:基本无障碍属性使用
场景:为按钮、输入框等组件添加无障碍属性,提高屏幕阅读器的识别能力。
实现步骤:
- 为按钮添加 aria-label 属性
- 为输入框添加 aria-labelledby 属性
- 为复杂组件添加 aria-describedby 属性
- 测试屏幕阅读器的识别效果
代码示例:
<template>
<view class="container">
<view class="form-item">
<text id="username-label">用户名</text>
<input
type="text"
placeholder="请输入用户名"
aria-labelledby="username-label"
/>
</view>
<view class="form-item">
<text id="password-label">密码</text>
<input
type="password"
placeholder="请输入密码"
aria-labelledby="password-label"
/>
</view>
<view class="button-group">
<button
class="login-button"
aria-label="登录按钮,点击后将提交表单"
>
登录
</button>
<button
class="cancel-button"
aria-label="取消按钮,点击后将重置表单"
>
取消
</button>
</view>
<view class="hint" id="login-hint">
<text>请输入正确的用户名和密码</text>
</view>
</view>
</template>
<style>
.container {
padding: 20rpx;
}
.form-item {
margin-bottom: 30rpx;
}
.form-item text {
display: block;
margin-bottom: 10rpx;
font-weight: bold;
}
.form-item input {
width: 100%;
padding: 20rpx;
border: 1rpx solid #ddd;
border-radius: 8rpx;
}
.button-group {
display: flex;
justify-content: space-between;
margin-bottom: 30rpx;
}
.button-group button {
flex: 1;
padding: 20rpx;
border-radius: 8rpx;
margin: 0 10rpx;
}
.login-button {
background-color: #007AFF;
color: white;
}
.cancel-button {
background-color: #f5f5f5;
color: #333;
}
.hint {
font-size: 24rpx;
color: #666;
}
</style>案例二:自定义组件的无障碍实现
场景:为自定义的开关组件添加无障碍属性,使其能够被屏幕阅读器正确识别。
实现步骤:
- 创建自定义开关组件
- 添加 aria-checked 属性指示状态
- 添加 aria-label 属性提供描述
- 实现键盘操作支持
代码示例:
<!-- components/AccessibleSwitch.vue -->
<template>
<view
class="switch-container"
:class="{ 'active': value }"
@click="toggle"
@keydown.enter="toggle"
@keydown.space="toggle"
tabindex="0"
role="switch"
:aria-checked="value"
:aria-label="label"
>
<view class="switch-track">
<view class="switch-thumb"></view>
</view>
<text v-if="showLabel" class="switch-label">{{ labelText }}</text>
</view>
</template>
<script>
export default {
name: 'AccessibleSwitch',
props: {
value: {
type: Boolean,
default: false
},
label: {
type: String,
default: '开关'
},
labelText: {
type: String,
default: ''
},
showLabel: {
type: Boolean,
default: false
}
},
methods: {
toggle() {
this.$emit('input', !this.value)
this.$emit('change', !this.value)
}
}
}
</script>
<style>
.switch-container {
display: flex;
align-items: center;
cursor: pointer;
outline: none;
}
.switch-track {
position: relative;
width: 120rpx;
height: 60rpx;
background-color: #ddd;
border-radius: 30rpx;
transition: background-color 0.3s;
}
.switch-container.active .switch-track {
background-color: #007AFF;
}
.switch-thumb {
position: absolute;
top: 4rpx;
left: 4rpx;
width: 52rpx;
height: 52rpx;
background-color: white;
border-radius: 50%;
transition: transform 0.3s;
}
.switch-container.active .switch-thumb {
transform: translateX(60rpx);
}
.switch-label {
margin-left: 20rpx;
font-size: 28rpx;
}
/* 焦点样式 */
.switch-container:focus {
box-shadow: 0 0 0 2rpx #007AFF;
}
</style>在页面中使用:
<template>
<view class="container">
<accessible-switch
v-model="notifications"
label="通知开关"
label-text="接收应用通知"
:show-label="true"
/>
<accessible-switch
v-model="location"
label="位置开关"
label-text="允许访问位置信息"
:show-label="true"
/>
<accessible-switch
v-model="bluetooth"
label="蓝牙开关"
label-text="启用蓝牙功能"
:show-label="true"
/>
</view>
</template>
<script>
import AccessibleSwitch from '@/components/AccessibleSwitch.vue'
export default {
components: {
AccessibleSwitch
},
data() {
return {
notifications: true,
location: false,
bluetooth: false
}
}
}
</script>
<style>
.container {
padding: 20rpx;
}
.container > view {
margin-bottom: 30rpx;
}
</style>案例三:键盘导航实现
场景:为应用添加键盘导航支持,允许用户使用键盘操作菜单和表单。
实现步骤:
- 添加 tabindex 属性控制焦点顺序
- 实现键盘事件处理
- 添加跳过导航链接
- 测试键盘导航效果
代码示例:
<template>
<view class="container">
<!-- 跳过导航链接 -->
<a href="#main-content" class="skip-link">跳过导航</a>
<!-- 导航菜单 -->
<view class="nav">
<view
class="nav-item"
:class="{ 'active': currentPage === 'home' }"
@click="navigate('home')"
@keydown.enter="navigate('home')"
@keydown.space="navigate('home')"
tabindex="0"
role="button"
:aria-label="`导航到首页,当前${currentPage === 'home' ? ' active' : ''}`"
>
首页
</view>
<view
class="nav-item"
:class="{ 'active': currentPage === 'products' }"
@click="navigate('products')"
@keydown.enter="navigate('products')"
@keydown.space="navigate('products')"
tabindex="0"
role="button"
:aria-label="`导航到产品页,当前${currentPage === 'products' ? ' active' : ''}`"
>
产品
</view>
<view
class="nav-item"
:class="{ 'active': currentPage === 'about' }"
@click="navigate('about')"
@keydown.enter="navigate('about')"
@keydown.space="navigate('about')"
tabindex="0"
role="button"
:aria-label="`导航到关于页,当前${currentPage === 'about' ? ' active' : ''}`"
>
关于
</view>
</view>
<!-- 主要内容 -->
<view id="main-content" class="main-content">
<text class="title">{{ pageTitle }}</text>
<view class="form">
<view class="form-item">
<text id="name-label">姓名</text>
<input
type="text"
placeholder="请输入姓名"
aria-labelledby="name-label"
tabindex="0"
/>
</view>
<view class="form-item">
<text id="email-label">邮箱</text>
<input
type="email"
placeholder="请输入邮箱"
aria-labelledby="email-label"
tabindex="0"
/>
</view>
<view class="button-group">
<button
class="submit-button"
@click="submit"
@keydown.enter="submit"
@keydown.space="submit"
tabindex="0"
role="button"
aria-label="提交表单"
>
提交
</button>
<button
class="reset-button"
@click="reset"
@keydown.enter="reset"
@keydown.space="reset"
tabindex="0"
role="button"
aria-label="重置表单"
>
重置
</button>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
currentPage: 'home',
pageTitle: '首页'
}
},
methods: {
navigate(page) {
this.currentPage = page
switch(page) {
case 'home':
this.pageTitle = '首页'
break
case 'products':
this.pageTitle = '产品页'
break
case 'about':
this.pageTitle = '关于页'
break
}
},
submit() {
uni.showToast({
title: '表单提交成功',
icon: 'success'
})
},
reset() {
// 重置表单逻辑
uni.showToast({
title: '表单已重置',
icon: 'none'
})
}
}
}
</script>
<style>
.container {
padding: 20rpx;
}
/* 跳过导航链接 */
.skip-link {
position: absolute;
top: -40rpx;
left: 0;
background-color: #007AFF;
color: white;
padding: 10rpx;
text-decoration: none;
z-index: 1000;
}
.skip-link:focus {
top: 0;
}
/* 导航菜单 */
.nav {
display: flex;
background-color: #f5f5f5;
border-radius: 8rpx;
padding: 0 10rpx;
margin-bottom: 30rpx;
}
.nav-item {
flex: 1;
padding: 20rpx;
text-align: center;
cursor: pointer;
}
.nav-item.active {
background-color: #007AFF;
color: white;
border-radius: 8rpx;
}
.nav-item:focus {
outline: 2rpx solid #007AFF;
border-radius: 8rpx;
}
/* 主要内容 */
.main-content {
margin-top: 20rpx;
}
.title {
font-size: 36rpx;
font-weight: bold;
margin-bottom: 30rpx;
}
/* 表单 */
.form {
margin-top: 20rpx;
}
.form-item {
margin-bottom: 30rpx;
}
.form-item text {
display: block;
margin-bottom: 10rpx;
font-weight: bold;
}
.form-item input {
width: 100%;
padding: 20rpx;
border: 1rpx solid #ddd;
border-radius: 8rpx;
}
.form-item input:focus {
outline: 2rpx solid #007AFF;
}
/* 按钮组 */
.button-group {
display: flex;
justify-content: space-between;
margin-top: 40rpx;
}
.button-group button {
flex: 1;
padding: 20rpx;
border-radius: 8rpx;
margin: 0 10rpx;
}
.submit-button {
background-color: #007AFF;
color: white;
}
.reset-button {
background-color: #f5f5f5;
color: #333;
}
.button-group button:focus {
outline: 2rpx solid #007AFF;
}
</style>无障碍访问最佳实践
1. 语义化标记
- 使用语义化标签:使用合适的 HTML 标签和 uni-app 组件
- 添加适当的角色:使用 role 属性指定元素的角色
- 保持结构清晰:使用清晰的层次结构组织内容
2. 颜色和对比度
- 足够的对比度:确保文本和背景的对比度符合 WCAG 标准
- 避免仅使用颜色:不要仅依赖颜色来传达信息
- 提供颜色调整选项:允许用户调整应用的颜色方案
3. 表单设计
- 明确的标签:为每个表单控件提供明确的标签
- 错误提示:提供清晰的错误提示和修复建议
- 键盘可访问:确保所有表单控件都可以通过键盘访问
4. 多媒体内容
- 替代文本:为图片提供 alt 属性
- 字幕和 transcripts:为视频和音频提供字幕和文字记录
- 音频描述:为视频提供音频描述,解释视觉内容
5. 动画和交互
- 可暂停的动画:允许用户暂停或关闭动画
- 足够的时间:为用户提供足够的时间阅读和交互
- 减少闪烁:避免可能引起癫痫发作的闪烁效果
- 提供反馈:为用户操作提供明确的反馈
6. 测试和验证
- 使用屏幕阅读器测试:在不同平台上测试屏幕阅读器的使用
- 键盘导航测试:测试完全使用键盘操作应用的能力
- 无障碍验证工具:使用工具如 axe、WAVE 等验证无障碍性
- 用户测试:邀请残障用户测试应用
7. 性能考虑
- 减少不必要的动画:避免可能影响屏幕阅读器性能的动画
- 优化焦点管理:确保焦点移动流畅,无延迟
- 减少网络请求:确保音频描述和字幕等内容加载迅速
总结回顾
本章节介绍了 uni-app 中的无障碍访问实现方法,包括:
- 无障碍访问基础概念:了解无障碍访问的重要性和基本概念
- uni-app 中的无障碍支持:了解 uni-app 提供的无障碍特性
- 无障碍属性的使用:掌握 aria-* 属性的使用方法
- 屏幕阅读器适配:了解如何适配不同平台的屏幕阅读器
- 键盘导航实现:掌握键盘导航的实现方法
- 无障碍访问最佳实践:学习无障碍设计的最佳实践
通过本章节的学习,您应该能够:
- 为 uni-app 应用添加基本的无障碍支持
- 使用无障碍属性增强组件的可访问性
- 实现键盘导航支持
- 适配不同平台的屏幕阅读器
- 遵循无障碍设计的最佳实践
无障碍访问是现代应用开发的重要组成部分,通过合理的无障碍实现,可以确保应用能够被所有用户使用,包括残障用户。在实际开发中,应根据应用的具体需求,结合 WCAG 指南,不断优化和完善无障碍功能,创建更加包容和友好的应用。