从删除注释按钮看Thorium Reader无障碍设计:3大痛点与优化方案
无障碍设计现状:删除注释功能的隐藏障碍
当视障用户通过屏幕阅读器操作Thorium Reader的注释删除功能时,会遭遇一系列"隐形障碍"。这些障碍并非功能缺陷,而是设计中对无障碍标准的忽视。通过分析src/renderer/reader/components/ReaderMenu.tsx核心代码,我们发现当前实现存在三大关键问题:
1. 操作链路的信息断层
删除注释的触发按钮(AlertDialog.Trigger)虽设置了基础aria-label,但缺乏状态变化反馈:
<AlertDialog.Trigger
className={stylesAnnotations.annotations_filter_trigger_button}
disabled={!annotationListFiltered.length}
title={__("dialog.deleteAnnotations")}
aria-label={__("dialog.deleteAnnotations")} // 静态标签,无动态状态提示
/>
当按钮因无选中注释而禁用时,屏幕阅读器无法自动播报"当前无可删除项",用户需手动探索状态。
2. 模态对话框的焦点陷阱
确认对话框(AlertDialog.Content)未实现无障碍焦点管理:
<AlertDialog.Content className={stylesAlertModals.AlertDialogContent}>
<AlertDialog.Title>删除注释</AlertDialog.Title>
<AlertDialog.Description>确定要删除选中的注释吗?</AlertDialog.Description>
<div className={stylesAlertModals.AlertDialogButtonContainer}>
<AlertDialog.Cancel>取消</AlertDialog.Cancel>
<AlertDialog.Action>确认删除</AlertDialog.Action>
</div>
</AlertDialog.Content>
对话框打开后焦点未自动移至"取消"按钮,键盘用户需多次Tab键才能到达操作区;删除完成后焦点未返回触发按钮,导致操作链路断裂。
3. 操作结果的不可感知性
删除操作通过dispatch(readerActions.note.remove.build(annotation))完成后,缺乏明确的屏幕阅读器反馈:
dispatch(readerActions.note.remove.build(annotation));
// 缺少操作结果的无障碍通知
用户无法通过听觉确认删除成功,需手动刷新注释列表才能验证操作效果。
技术优化方案:构建无障碍闭环
1. 动态状态感知系统
为删除按钮添加实时状态提示,通过aria-live区域播报状态变化:
// 在组件顶部添加状态播报区域
<div aria-live="polite" className="sr-only" id="delete-status-announcer" />
// 修改触发按钮
<AlertDialog.Trigger
className={stylesAnnotations.annotations_filter_trigger_button}
disabled={!annotationListFiltered.length}
title={__("dialog.deleteAnnotations")}
aria-label={__("dialog.deleteAnnotations")}
aria-describedby={!annotationListFiltered.length ? "delete-disabled-reason" : undefined}
/>
{!annotationListFiltered.length && (
<span id="delete-disabled-reason" className="sr-only">
{__("dialog.noAnnotationsToDelete")}
</span>
)}
当按钮状态变化时,通过aria-live区域自动播报状态,无需用户主动查询。
2. 符合WCAG 2.1的焦点管理
重构对话框焦点逻辑,实现"打开-操作-关闭"全链路焦点控制:
const cancelButtonRef = useRef<HTMLButtonElement>(null);
useEffect(() => {
if (dialogOpen) {
// 对话框打开后聚焦取消按钮
cancelButtonRef.current?.focus();
// 监听Escape键关闭
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Escape') setDialogOpen(false);
};
window.addEventListener('keydown', handleKeyDown);
return () => window.removeEventListener('keydown', handleKeyDown);
}
}, [dialogOpen]);
// 删除完成后返回焦点
const handleDelete = () => {
dispatch(readerActions.note.remove.build(annotation));
setDialogOpen(false);
triggerButtonRef.current?.focus(); // 返回触发按钮
};
// 对话框内容
<AlertDialog.Content className={stylesAlertModals.AlertDialogContent}>
<AlertDialog.Title>删除注释</AlertDialog.Title>
<AlertDialog.Description>确定要删除选中的{annotationListFiltered.length}条注释吗?</AlertDialog.Description>
<div className={stylesAlertModals.AlertDialogButtonContainer}>
<AlertDialog.Cancel ref={cancelButtonRef}>取消</AlertDialog.Cancel>
<AlertDialog.Action onClick={handleDelete}>确认删除</AlertDialog.Action>
</div>
</AlertDialog.Content>
3. 操作结果的多模态反馈
结合Toast组件实现听觉+视觉双重反馈:
import { toastActions } from "readium-desktop/common/redux/actions";
import { ToastType } from "readium-desktop/common/models/toast";
// 删除操作后触发无障碍通知
dispatch(readerActions.note.remove.build(annotation));
dispatch(toastActions.openRequest.build(
ToastType.Success,
__("reader.annotations.deleteSuccess", {count: annotationListFiltered.length})
));
// 在Toast组件中强化无障碍属性 (src/renderer/common/components/toast/Toast.tsx)
<div
className={stylesToasts.toast}
role="alert" // 声明为警告区域
aria-live="assertive" // 紧急内容优先播报
aria-atomic="true" // 整体内容更新时重新播报
>
{message}
</div>
role="alert"与aria-live="assertive"的组合确保屏幕阅读器立即播报删除结果,aria-atomic="true"保证完整句子被正确朗读。
量化优化效果:从合规到体验升级
WCAG 2.1标准符合性对比
| 评估项 | 当前实现 | 优化方案 | 合规标准 |
|---|---|---|---|
| 操作提示 | 静态标签 | 动态状态播报 | 3.3.1 错误识别(A级) |
| 焦点管理 | 无焦点控制 | 自动焦点定位 | 2.4.3 焦点顺序(A级) |
| 状态通知 | 无反馈机制 | ARIA实时区域 | 4.1.3 状态消息(AA级) |
| 键盘导航 | 可聚焦但无序 | 逻辑焦点链路 | 2.1.1 键盘(A级) |
开发实施清单
-
组件改造(优先级:高)
- 为删除按钮添加动态
aria-describedby - 实现对话框焦点自动管理
- 强化Toast组件的无障碍属性
- 为删除按钮添加动态
-
测试验证(优先级:中)
- 使用NVDA+Firefox测试完整操作链路
- 验证键盘仅操作的可行性
- 检查屏幕阅读器播报文本的自然度
-
扩展适配(优先级:低)
- 将优化方案抽象为
AccessibleAlertDialog通用组件 - 应用于书签删除、批量操作等类似场景
- 将优化方案抽象为
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



