一、引言:模态交互中的焦点难题
在Web应用中,模态对话框、弹出菜单、侧边栏等覆盖层组件非常普遍。当这些组件打开时,一个基本的要求是用户只能与当前覆盖层交互,而背景内容应当“隐藏”起来——不仅是视觉上的遮盖,更关键的是交互上的阻断:键盘导航不应进入背景区域,屏幕阅读器也不应朗读背景内容。传统上,开发者需要编写大量JavaScript来实现焦点陷阱、管理Tab键循环、动态设置aria-hidden属性,稍有不慎就会造成无障碍缺陷。
HTML新引入的inert属性为这一问题提供了原生解决方案。将inert属性添加到任何元素上,会立即使该元素及其所有子元素变为“惰性”——浏览器会将其从焦点顺序中移除,阻止任何形式的交互,同时向辅助技术标记为不可操作。结合<dialog>元素和Popover API,我们可以在极少量甚至零JavaScript的情况下实现完美的模态焦点管理。
二、inert 属性的基本行为
inert是一个布尔属性,可以添加到任意HTML元素上。当元素被标记为inert时,会发生以下变化:
- 阻止交互事件:该元素及其后代上的点击、输入等事件都不会触发。
- 移出焦点顺序:用户使用Tab键导航时会跳过整个inert子树,就像它们不存在一样。
- 可访问性树中标记:屏幕阅读器会忽略inert元素的内容,除非显式地通过其他方式聚焦。
- 继承性:inert属性会隐式地应用到所有后代元素上。
使用方式非常简单,直接在HTML中写入inert即可:
<div inert>
<button>这个按钮无法被点击</button>
<a href="#" rel="external nofollow" >这个链接也无法聚焦</a>
</div>
也可以通过JavaScript动态添加或移除:
document.getElementById('main-content').inert = true;
需要注意的是,inert不会影响元素本身的视觉样式,通常需要配合CSS(如降低透明度)来给出视觉上的反馈。
三、结合 <dialog> 实现无脚本模态焦点控制
<dialog>元素是HTML原生的模态对话框,它具备基本的焦点管理和Esc键关闭能力。但我们仍然需要防止用户跳出对话框后误操作背景内容。一个很常见的做法是,当对话框打开时,将页面主体内容设置为inert。这可以通过极少量的JavaScript配合dialog的open属性来完成。
3.1 基本结构
<!-- 主内容区域 -->
<main id="main-content">
<h1>页面主内容</h1>
<button id="open-dialog">打开对话框</button>
</main>
<!-- 模态对话框 -->
<dialog id="my-dialog">
<h2>确认操作</h2>
<p>确定要执行此操作吗?</p>
<form method="dialog">
<button type="submit" value="confirm">确认</button>
<button type="submit" value="cancel">取消</button>
</form>
</dialog>
3.2 少量JavaScript实现inert切换
const dialog = document.getElementById('my-dialog');
const mainContent = document.getElementById('main-content');
const openBtn = document.getElementById('open-dialog');
openBtn.addEventListener('click', () => {
dialog.showModal();
mainContent.inert = true; // 设置主内容为惰性
});
dialog.addEventListener('close', () => {
mainContent.inert = false; // 恢复主内容
// 焦点自动回到触发按钮,无需额外处理
});
这样,当对话框显示时,主内容区域完全无法交互:用户无法通过Tab键进入任何主内容中的链接或表单控件,也无法用鼠标点击它们。同时屏幕阅读器用户也不会意外浏览到背景文本。当对话框关闭时,inert被移除,一切恢复正常。整个过程只需要几行脚本,且不涉及复杂的焦点陷阱代码。
3.3 完全无JavaScript的方案:利用Popover
如果我们希望进一步减少JavaScript,可以结合Popover API实现自动的打开关闭,但inert仍需要一点脚本来切换。不过对于简单的场景,浏览器内置的对话框行为已足够;如果只使用Popover,popover本身已具备轻量级的背景阻塞能力——但inert在需要更严格的可访问性控制时仍然是更优解。
四、实用案例:侧边栏导航与inert
除了模态对话框,侧边栏(Drawer)也是移动端常见的一种覆盖层。当侧边栏滑入时,主内容也应该被标记为inert,以防止焦点进入背景。以下是一个用少量JavaScript实现的示例:
<div class="layout">
<aside id="sidebar" class="sidebar">
<nav>
<a href="/" rel="external nofollow" >首页</a>
<a href="/about" rel="external nofollow" >关于</a>
</nav>
</aside>
<main id="main">
<button id="toggle-sidebar">菜单</button>
<!-- 页面主内容 -->
</main>
</div>
<script>
const toggleBtn = document.getElementById('toggle-sidebar');
const main = document.getElementById('main');
const sidebar = document.getElementById('sidebar');
let open = false;
toggleBtn.addEventListener('click', () => {
open = !open;
sidebar.classList.toggle('open', open);
main.inert = open; // 打开时主内容惰性,关闭时恢复
});
</script>
配合适当的CSS过渡,用户打开侧边栏后,Tab焦点会被限制在侧边栏内部,无法跳转到背景主内容。这也符合WCAG关于焦点管理的可访问性要求。
五、inert 与 aria-hidden 的对比
在inert出现之前,开发者常使用aria-hidden=”true”来对辅助技术隐藏元素。但两者有着本质区别:
- aria-hidden 只影响屏幕阅读器,不阻止键盘导航或鼠标交互。用户仍可能Tab进入被隐藏的元素,造成焦点迷失。
- inert 同时阻止了交互、焦点和辅助技术访问,提供了完整的隔离。
- 建议在模态交互中使用 inert,在无需完全阻断交互但需要从可访问性树中移除视觉装饰元素时使用 aria-hidden。
现代浏览器对inert的支持已经非常广泛,包括Chrome、Edge、Firefox、Safari均提供完整支持。这使得它在生产环境中已经成熟可用。
六、性能与渐进增强
inert属性的处理由浏览器原生实现,性能开销极低。对于不支持inert的旧浏览器,可以使用一个小型的polyfill来模拟行为。但考虑到今天主流浏览器都已支持,多数项目可以直接使用,并在必要时提供回退。
在开发中,建议遵循“渐进增强”原则:首先保证页面的基本功能在不支持inert的浏览器中仍然可用(例如仍能打开对话框,但焦点管理可能略弱),然后在支持的浏览器中自动获得更强的可访问性体验。
七、总结
HTML的inert属性为Web可访问性带来了重大改进,它通过几行代码就解决了长期以来需要复杂JavaScript才能解决的模态焦点管理难题。无论是配合<dialog>实现对话框,还是管理侧边栏、抽屉等覆盖层,inert都能让你的组件更安全、更易于被所有用户使用。随着Web标准的发展,越来越多的原生能力正在帮助我们构建更健壮、更包容的界面。现在正是将inert引入你项目的最佳时机。

