无障碍焦点管理:弹窗打开以后,键盘用户不能迷路

无障碍焦点管理:弹窗打开以后,键盘用户不能迷路

前端无障碍里,焦点管理经常被忽略。弹窗打开后,键盘焦点还停在背景按钮;关闭弹窗后,焦点不知道回到哪里;抽屉里按 Tab 跑到页面底部。这些问题不会影响鼠标用户,却会让键盘用户和读屏用户迷路。

焦点管理不是锦上添花,而是交互组件的基本功能。尤其是弹窗、抽屉、菜单、浮层,必须认真处理。

一、弹窗焦点要形成闭环

flowchart TD
  A[Trigger Button] --> B[Open Dialog]
  B --> C[Move Focus Into Dialog]
  C --> D[Trap Focus]
  D --> E[Close Dialog]
  E --> F[Restore Focus To Trigger]

打开时焦点进入弹窗,弹窗内 Tab 循环,关闭后回到触发按钮。这个闭环缺一环,键盘用户都会失去上下文。

二、使用语义属性

弹窗应该有正确的 role、标题和模态属性。

<div role="dialog" aria-modal="true" aria-labelledby="dialog-title">
  <h2 id="dialog-title">删除项目</h2>
  <button>取消</button>
  <button>确认删除</button>
</div>

如果没有可访问标题,读屏用户只听到“对话框”,不知道它在处理什么任务。

三、实现焦点陷阱

可以使用成熟库,也可以自己实现基本逻辑。核心是找到弹窗内可聚焦元素,并在首尾循环。

const focusable = dialog.querySelectorAll(
  'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
);

const first = focusable[0];
const last = focusable[focusable.length - 1];

自己实现时要考虑动态内容、disabled 元素和嵌套浮层。复杂组件建议使用经过验证的无障碍基础库。

四、关闭后恢复焦点

弹窗打开前保存触发元素,关闭后恢复。

const trigger = document.activeElement;
openDialog();

function closeDialog() {
  dialog.remove();
  trigger?.focus?.();
}

如果触发元素已经不存在,比如删除了当前列表项,就应该把焦点移动到合理的替代位置,例如列表容器或下一个操作按钮。

还要处理 Escape 和背景滚动。模态弹窗打开时,Escape 通常应关闭弹窗,页面背景不应继续滚动。关闭动作也要和焦点恢复配合,不能先移除节点再丢失引用。

dialog_keyboard_contract:
  Tab: cycle inside dialog
  Shift+Tab: cycle backward
  Escape: close when allowed
  Close: restore focus
  Background: inert or unavailable

这份键盘契约应写进组件测试,而不是只靠人工试用。

五、总结

无障碍焦点管理要保证弹窗打开后焦点进入、Tab 不逃出、关闭后焦点恢复。配合正确的 role="dialog"aria-modal 和可访问标题,组件才真正可操作。

好看的浮层不等于好用的浮层。键盘用户不迷路,才说明交互边界被认真设计过。

焦点管理做得好,鼠标用户通常不会察觉;但对键盘和读屏用户,它就是能不能完成任务的分界线。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值