无障碍 (A11y)
Directix 提供全面的无障碍工具,帮助您构建适用于所有人的包容性指令体验。
概述
v1.10.0 的无障碍模块包括:
- ARIA 属性管理 - 应用和管理 ARIA 属性
- 屏幕阅读器支持 - 向屏幕阅读器公告消息
- 键盘导航 - 使用键盘导航指令元素
- 焦点管理 - 在组件内陷阱和管理焦点
ARIA 属性
applyAriaAttributes
将 ARIA 属性应用到元素,支持自动清理。
typescript
import { applyAriaAttributes } from 'directix'
// 应用 ARIA 属性
applyAriaAttributes(element, {
role: 'button',
ariaLabel: '提交表单',
ariaDisabled: true,
tabIndex: 0,
})ARIA 配置
ARIAConfig 接口支持所有标准 ARIA 属性:
typescript
interface ARIAConfig {
// 角色
role?: ARIARole // 支持 60+ ARIA 角色
// 状态属性
ariaExpanded?: boolean
ariaSelected?: boolean
ariaChecked?: boolean
ariaDisabled?: boolean
ariaHidden?: boolean
ariaBusy?: boolean
ariaPressed?: boolean
ariaCurrent?: 'page' | 'step' | 'location' | 'date' | 'time' | boolean
// 属性属性
ariaLabel?: string
ariaLabelledBy?: string
ariaDescribedBy?: string
ariaControls?: string
ariaOwns?: string
ariaHasPopup?: 'menu' | 'listbox' | 'tree' | 'grid' | 'dialog' | 'tooltip' | boolean
// 实时区域属性
ariaLive?: 'off' | 'polite' | 'assertive'
ariaAtomic?: boolean
ariaRelevant?: 'additions' | 'removals' | 'text' | 'all'
// 值属性
ariaValueNow?: number
ariaValueMin?: number
ariaValueMax?: number
ariaValueText?: string
// 其他属性
ariaPlaceholder?: string
ariaRequired?: boolean
ariaReadonly?: boolean
ariaModal?: boolean
tabIndex?: number
}clearAriaAttributes
移除元素上的所有 ARIA 属性:
typescript
import { clearAriaAttributes } from 'directix'
clearAriaAttributes(element)自动生成 ARIA 配置
为常见指令类型生成默认 ARIA 配置:
typescript
import { getAutoAriaConfig } from 'directix'
// 工具提示
const tooltipConfig = getAutoAriaConfig({
type: 'tooltip',
expanded: false,
})
// 对话框
const dialogConfig = getAutoAriaConfig({
type: 'dialog',
label: '确认对话框',
})支持类型:tooltip、menu、dialog、popover、dropdown、modal、alert、region
屏幕阅读器支持
announce
向屏幕阅读器用户公告消息:
typescript
import { announce } from 'directix'
// 基本公告
announce('表单提交成功')
// 带优先级(polite 等待用户暂停,assertive 立即打断)
announce('错误:请修正表单', { priority: 'assertive' })
// 自定义超时时间
announce('已添加到购物车', { timeout: 2000 })公告选项
| 选项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
priority | 'polite' | 'assertive' | 'polite' | 公告优先级 |
timeout | number | 1000 | 清除消息前的等待时间(毫秒) |
clear | boolean | true | 超时后清除消息 |
键盘导航
useKeyboardNavigation
指令元素键盘导航的组合式函数:
vue
<script setup>
import { ref, onMounted } from 'vue'
import { useKeyboardNavigation } from 'directix'
const containerRef = ref(null)
const items = ref([])
const { focusedIndex, bind, focusNext, focusPrev } = useKeyboardNavigation({
focusTrap: true,
loop: true,
rovingTabindex: true,
})
onMounted(() => {
if (containerRef.value) {
bind(containerRef.value, items.value)
}
})
</script>
<template>
<div ref="containerRef" role="listbox">
<div
v-for="(item, index) in items"
:key="item.id"
ref="items"
role="option"
@click="selectItem(index)"
>
{{ item.label }}
</div>
</div>
</template>导航配置
| 选项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
nextKeys | string[] | ['ArrowDown', 'ArrowRight'] | 下一个元素的按键 |
prevKeys | string[] | ['ArrowUp', 'ArrowLeft'] | 上一个元素的按键 |
selectKeys | string[] | ['Enter', ' '] | 选择按键 |
closeKeys | string[] | ['Escape'] | 关闭按键 |
focusTrap | boolean | false | 启用焦点陷阱 |
loop | boolean | true | 循环导航 |
rovingTabindex | boolean | false | 使用漫游 tabindex |
returnFocus | boolean | true | 关闭时返回焦点 |
焦点管理
useFocusTrap
在容器内陷阱焦点的组合式函数:
vue
<script setup>
import { ref } from 'vue'
import { useFocusTrap } from 'directix'
const modalRef = ref(null)
const isOpen = ref(false)
const { activate, deactivate, isActive } = useFocusTrap(modalRef, {
initialFocus: '[data-autofocus]',
returnFocus: true,
escapeDeactivates: true,
onActivate: () => console.log('焦点陷阱已激活'),
onDeactivate: () => console.log('焦点陷阱已取消'),
})
function openModal() {
isOpen.value = true
activate()
}
function closeModal() {
deactivate()
isOpen.value = false
}
</script>
<template>
<button @click="openModal">打开模态框</button>
<div v-if="isOpen" ref="modalRef" role="dialog" aria-modal="true">
<h2>模态框标题</h2>
<input data-autofocus placeholder="第一个输入框" />
<button @click="closeModal">关闭</button>
</div>
</template>焦点陷阱选项
| 选项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
initialFocus | HTMLElement | string | Function | - | 初始焦点元素 |
allowOutsideClick | boolean | Function | false | 允许外部点击 |
escapeDeactivates | boolean | true | Escape 键取消 |
onActivate | Function | - | 激活回调 |
onDeactivate | Function | - | 取消回调 |
最佳实践
1. 始终提供标签
typescript
// 好的做法
applyAriaAttributes(button, {
role: 'button',
ariaLabel: '提交表单',
})
// 避免 - 屏幕阅读器可能无法读取任何内容
element.setAttribute('role', 'button')2. 正确使用实时区域
typescript
// 可等待的状态更新
announce('正在保存...', { priority: 'polite' })
// 紧急错误
announce('发生严重错误!', { priority: 'assertive' })3. 实现键盘导航
所有交互元素都应可通过键盘访问:
- 列表和菜单使用
useKeyboardNavigation - 模态框和对话框使用
useFocusTrap - 提供可见的焦点指示器
4. 使用屏幕阅读器测试
始终使用实际屏幕阅读器测试您的指令:
- macOS: VoiceOver
- Windows: NVDA 或 JAWS
- Linux: Orca
- 移动端: VoiceOver (iOS) 或 TalkBack (Android)
工具函数
| 函数 | 说明 |
|---|---|
applyAriaAttributes | 将 ARIA 属性应用到元素 |
clearAriaAttributes | 移除所有 ARIA 属性 |
generateAriaId | 生成唯一 ARIA ID |
announce | 向屏幕阅读器公告 |
clearAnnouncer | 清除屏幕阅读器公告器 |
getAutoAriaConfig | 自动生成 ARIA 配置 |
useKeyboardNavigation | 键盘导航组合式函数 |
useFocusTrap | 焦点陷阱组合式函数 |