题目与场景
模态框是当前活动界面,背景仍可见但不可用。实现要保留触发按钮,支持 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,应使用合适的展开语义。
触发按钮消失怎么办?
把焦点恢复到最近的合理控件,例如更新后的行或页面标题,并在需要时播报结果。不要聚焦已脱离或禁用的元素。