代表性面试主题

前端面试:如何用 inert 实现可访问的模态框?

前端中等
Offer.cc 编辑团队发布 更新

题干

仪表盘上方打开一个模态编辑器。如何阻止键盘、指针和辅助技术用户操作背后的页面,同时保证焦点恢复?

题目与场景

模态框是当前活动界面,背景仍可见但不可用。实现要保留触发按钮,支持 Escape 和关闭控件,避免依赖复杂的正 tabindex。

面试官考察什么

  • 理解 inert 阻止什么,以及它不会自动配置什么。
  • 结合原生对话框语义、焦点管理和可访问名称。
  • 处理 Portal、动画时序、浏览器支持和减少动效。

作答前的澄清问题

  • 这是模态对话框,还是允许操作背景的非模态浮层?
  • 对话框归属哪个元素,Portal 挂载在哪里?
  • 打开、取消、提交和关闭时焦点应该去哪里?
  • 是否需要兼容旧浏览器?

30 秒回答框架

支持时我会使用原生模态对话框,把焦点移到有名称的标题或首个有效控件,并在打开期间只给对话框外的应用壳设置 inert。恢复焦点到仍存在的触发按钮,提供 Escape 和明确的关闭按钮,并测试键盘与读屏路径。inert 负责阻止背景交互,不代替命名、焦点恢复或状态管理。

分步深挖

1. 选择正确语义表面

浏览器基线允许时,真正的模态使用原生 dialog 元素和 showModal(),通过可见标题或 aria-labelledby 提供名称。非模态展开不应让整页 inert,因为用户预期在界面之间移动。

2. 隔离背景

inert 设置到对话框外的应用容器,不要设置到同时包含 Portal 对话框的公共祖先。浏览器会让 inert 子树离开焦点导航并阻止焦点事件,也阻止正常用户交互。不要在包含当前焦点对话框的祖先上散落 aria-hidden

3. 管理焦点生命周期

打开前保存触发按钮。打开后将焦点放到稳定标题或首个任务控件,而不是随意的关闭图标。取消或提交成功后关闭、移除 inert,若触发按钮仍可见且启用则恢复焦点;否则选择附近合理目标并在需要时播报结果。

4. 处理 Portal 与过渡

将对话框渲染到顶层,避免裁剪和层叠上下文限制。交互前先设置 inert,挂载后再设置焦点。根据减少动效偏好协调进出动画,任何中断路径都不能留下背景锁定。

5. 测试失败路径

测试 Tab、Shift+Tab、Escape、关闭按钮、校验错误、嵌套对话框、路由变化和读屏播报。确认点击、程序化焦点、页内查找和指针事件符合浏览器的 inert 行为。只有兼容范围需要时才加回退实现。

高质量示范回答

“真正的模态我会使用原生对话框,把焦点放到有名称的标题或首个任务控件,并把 inert 设置在应用壳上,确保 Portal 对话框不在 inert 子树内。打开前保存触发按钮,关闭后恢复焦点,支持 Escape 和明确关闭按钮。焦点、命名和校验独立于 inert;还要测试键盘、读屏、Portal、动画和路由变化,并在中断时清理 inert。”

常见误区

  • 把 inert 设置到包含 Portal 的应用根 → 对话框也会 inert → 把对话框挂在 inert 子树之外。
  • 把 inert 当作焦点管理器 → 打开或关闭时焦点仍可能错误 → 定义完整焦点生命周期。
  • 背景含焦点时使用 aria-hidden → 辅助技术状态不一致 → 先移动焦点,再让背景 inert。
  • 忘记中断动画 → 页面会一直被锁定 → 在每条关闭和卸载路径清除 inert。

追问与回答

inert 会取代焦点陷阱吗?

它让背景不可聚焦,因此模态通常不必手写焦点陷阱。但仍要设置初始焦点、恢复焦点、命名对话框,并准备浏览器或组件回退。

非模态抽屉能用 inert 吗?

只有产品明确要求背景不可用时才用。允许同时操作背景的抽屉不应让页面 inert,应使用合适的展开语义。

触发按钮消失怎么办?

把焦点恢复到最近的合理控件,例如更新后的行或页面标题,并在需要时播报结果。不要聚焦已脱离或禁用的元素。

公开来源

同类题目