Vue 无障碍弹窗:焦点、Escape 与背景隔离是一套状态机

Vue 无障碍弹窗:焦点、Escape 与背景隔离是一套状态机原创封面

先确定对话框是否真的需要模态

模态对话框会暂时阻断页面其余部分,要求用户先完成、取消或理解当前任务,适合危险确认、短表单和必须立即决策的流程。帮助说明、非关键筛选或持续参考内容通常更适合非模态浮层、抽屉或页面内区域。错误地把所有浮层都做成模态,会迫使键盘与读屏用户反复穿越焦点边界,也让移动端软键盘下的可用空间更紧张。

打开模态后,视觉上被遮罩的背景也必须从交互与辅助技术导航中退出。仅绘制半透明遮罩不能阻止键盘聚焦后方链接,给容器设置禁止指针也不会影响读屏浏览。优先使用平台提供的对话框能力或对背景应用可靠的惰性状态,并处理不支持环境;不要只在背景根节点加一个无障碍隐藏属性而把对话框也一起隐藏。

组件契约要说明标题来源、描述关系、初始焦点策略、是否允许按退出键关闭、遮罩点击行为以及关闭原因。危险操作可以禁止遮罩误触,却仍应提供可聚焦的取消按钮。业务层依据关闭原因决定丢弃草稿或保留输入,组件本身不应把所有关闭都简化成同一个布尔事件。

挂载完成后再安置初始焦点

对话框进入文档并完成一次 Vue 更新后才能可靠聚焦内部节点。打开状态变化时等待 DOM 更新,再验证目标仍在当前对话框、可见且可聚焦,然后调用聚焦。若入场动画改变可见性,不应盲目等待固定毫秒;焦点通常可在结构挂载后立即进入,视觉动画只负责呈现,避免用户已经按键而焦点还留在背景。

初始焦点不总是第一个输入。不可逆确认应先聚焦安全的取消操作或对话框标题附近的静态说明,防止回车立即触发危险按钮;长说明内容可让带临时聚焦能力的标题获得焦点,使读屏从语义开头阅读。普通短表单则可聚焦第一个有意义且未禁用的控件,但有校验错误时优先定位错误摘要或首个无效字段。

自动聚焦必须尊重竞态:对话框在等待更新期间可能已关闭,嵌套对话框可能成为新的最上层,目标也可能因条件渲染消失。聚焦前核对实例打开令牌和栈顶身份,不能让迟到回调把焦点从新对话框抢回旧节点。测试中连续快速打开、关闭和切换内容,能暴露只在慢设备出现的焦点跳回。

焦点环绕基于实时可聚焦集合

模态打开时,Tab 和反向 Tab 应在当前对话框可用控件之间循环。候选集合需要排除禁用、隐藏、惰性区域和不可见节点,并包含通过合理顺序进入的原生控件及显式可聚焦元素。不要在首次挂载时永久缓存列表,因为校验消息、条件字段、异步按钮和折叠区域都会改变可聚焦集合。

当焦点位于最后一个候选并向前移动时回到第一个,位于第一个并反向移动时回到最后。若对话框暂时没有可操作控件,容器本身要具备程序化焦点能力,并成为稳定落点,不能让 Tab 逃回地址栏后的背景页面。按键处理只拦截未被修饰的 Tab,组合键和输入法事件不应被误吞。

仅监听对话框内部的按键仍可能遗漏浏览器、扩展或脚本把焦点移到外部。顶层模态存续期间可监听文档焦点变化,发现焦点落到背景时拉回最近的合法节点;但要避免和浏览器原生对话框机制重复争夺。恢复策略应记录对话框内最后焦点,内容仍存在时回到它,否则选择当前首个候选。

关闭后把焦点归还给仍有效的发起者

打开前记录实际触发元素,而不是假定某个固定选择器。关闭且 DOM 移除后,若触发者仍连接在文档、未禁用且可见,就把焦点还给它。列表中的删除按钮可能随确认操作一起消失,此时应由业务提供后备目标,例如下一行操作按钮、列表标题或新状态提示,避免焦点坠回文档开头。

焦点恢复只由当前栈顶对话框执行。嵌套确认框关闭时应回到父对话框内的触发按钮,而父对话框关闭时才回到页面。若每个实例都在自己的关闭侦听中无条件恢复,动画和状态批处理会让多个回调相互抢焦点。维护明确的对话框栈和每层发起者,可以把顺序从时机猜测变成数据。

路由变化是特殊关闭原因:新页面可能已接管焦点并宣告标题,此时把焦点恢复到旧页面节点既无效又可能打断导航。组件收到导航关闭时跳过常规恢复,由路由后的页面焦点策略负责。验证浏览器后退、保存后跳转、删除后留在原页三种路径,确认每条路径只有一个焦点所有者。

标题、描述和错误状态形成可读语义

对话框需要可访问名称,通常由可见标题元素通过标识关联。没有可见标题的设计应提供明确名称,但纯图标关闭按钮的标签不能替代整个对话框标题。描述关系只关联简短说明;把数百字正文全部作为描述,会让读屏在用户到达任何控件前一次性朗读,长内容应保留为普通结构供顺序浏览。

标题和描述标识要在服务端与客户端稳定,尤其是 Nuxt 服务端渲染页面。模块级自增值在并发请求与水合之间可能不同,使关联失效并产生水合警告。使用稳定标识机制或由实例输入派生,重复列表内仍要保证唯一。组件卸载后也不要让外部按钮保留指向已不存在对话框的控制关系。

表单提交失败时保持对话框打开,更新错误摘要并将焦点移到摘要或首个无效字段,同时用适度的实时区域宣告新增错误。不要每次键入都重新聚焦或朗读全部表单。异步保存期间禁用重复提交但保留取消策略;失败后恢复按钮与焦点,成功关闭前则先确定下一个页面状态和焦点落点。

滚动锁定与移动视口不能破坏位置

模态打开时通常需要阻止背景滚动,但直接给页面设置固定定位可能让当前滚动位置跳到顶部。记录滚动偏移和原有内联样式,锁定时补偿位置,关闭最后一个模态后再精确恢复;嵌套对话框使用引用计数,不能子层关闭就提前解锁背景。滚动条消失造成的页面宽度变化也需补偿,避免内容横向跳动。

对话框内容本身应在动态视口内滚动,标题和主要操作可按需要保持可见。使用安全区域内边距照顾刘海与底部手势区,长英文、链接和代码允许在内容区域换行或局部滚动,不能把整个页面撑宽。软键盘出现时,固定像素高度可能遮住当前输入,应依据动态视口调整最大高度并让浏览器滚动聚焦字段进入可见区域。

触摸滚动要允许对话框内部长内容自然滚动,同时阻止到达边界后把背景页面带动。不要用全局阻止触摸默认行为,因为这会损害缩放、选择文本和辅助操作。分别在小屏、横屏、两百分比缩放和超长验证消息下检查关闭按钮、标题、错误与提交操作都能到达。

自动化与人工输入共同验证焦点协议

组件测试通过真实按键序列打开对话框,断言初始焦点、正反向循环、退出键策略和关闭后的恢复,而不是直接调用内部方法。动态添加、禁用和移除控件后再次循环,证明候选集合实时计算。构造触发者被删除、嵌套两层及快速开关竞态,检查任何时刻焦点都在当前顶层或规定的后备目标。

自动无障碍检查可以发现名称缺失、关联无效和部分角色错误,却无法证明焦点顺序符合任务。人工矩阵至少使用纯键盘和一种读屏器,验证打开时宣告标题、背景不可浏览、长内容可逐段阅读、错误被适量提示以及关闭后上下文连续。鼠标、触摸和语音控制也要能找到同一可见操作,不设计只对键盘存在的隐藏出口。

浏览器回归覆盖服务端首屏已包含对话框、客户端点击后挂载、路由切换中关闭、移动端软键盘和减少动画偏好。记录焦点事件顺序和控制台水合警告,确保修复不是依靠延迟或吞掉按键。最终验收标准是用户从发起操作到返回上下文全程可预测,而不只是页面上存在一个正确的对话框角色。

实现片段

dialogRef.value?.showModal()

一手参考资料

评论 · 0

还没有评论留下第一句经过思考的话。

游客评论需审核。注册后可直接公开,无需审核。

SHARE / 分享

分享这篇文章

WECHAT / 微信

用微信扫一扫

在手机微信中打开文章后,再从微信右上角分享给朋友或朋友圈。