做前端开发的人对“模态打开后背景不能点”这个需求太熟悉了。传统做法是:给背景容器加pointer-events: none,把里面所有可聚焦元素的tabindex设为-1,再加上aria-hidden="true"让屏幕阅读器跳过。功能勉强实现,但代码很容易漏——只要背景里有一个漏网的可聚焦元素,用户就能按Tab键绕到背景上。
HTML标准在2022年正式引入的inert属性,用一个单词就把上面所有操作打包了:标记为inert的元素及其所有子元素会立即失去交互能力——鼠标点击无效、键盘聚焦跳过、辅助技术也会完全忽略它们。这篇就从三个真实场景出发,把inert的用法和搭配逻辑讲透。
一、inert属性的作用和意义
inert是一个布尔HTML属性,可以直接写在任何元素上。它的效果可以概括为三重禁止:
- 交互禁止:元素上的点击、输入等交互事件都不会触发。但要注意,它不会改变鼠标指针样式,那个需要额外CSS处理。
- 聚焦禁止:元素及其子元素无法通过Tab键、
focus()调用或任何方式获得焦点。原本可聚焦的控件会变成不可聚焦,就像不存在一样。 - 无障碍忽略:屏幕阅读器会完全跳过这个区域,等价于同时施加了
aria-hidden="true"和阻止焦点。
一个极其简短的例子:
<div inert>
<button onclick="alert('这个按钮点不了')">点我</button>
<a href="/danger" rel="external nofollow" >危险链接</a>
</div>
当inert存在时,按钮和链接都无法交互,也不能被Tab键聚焦。移掉inert属性,它们立刻恢复正常。整个过程不需要操作任何子元素的属性。
二、案例一:模态对话框冻结背景
假设页面上有一个模态详情弹窗,打开时应该让背景内容不可操作。传统方案需要在打开弹窗时遍历所有背景节点的可聚焦元素并设置tabindex="-1",关闭时恢复。inert的做法是直接把背景包在一个容器里,打开弹窗时给它加上inert。
2.1 HTML结构
<main id="app-content">
<h1>页面标题</h1>
<button>普通操作</button>
<a href="/somewhere" rel="external nofollow" >跳转链接</a>
</main>
<dialog id="modal">
<h2>模态内容</h2>
<p>这里是可以交互的区域</p>
<button id="close-modal">关闭</button>
</dialog>
2.2 交互脚本
const appContent = document.getElementById('app-content');
const modal = document.getElementById('modal');
function openModal() {
appContent.setAttribute('inert', ''); // 背景区域冻结
modal.showModal();
}
function closeModal() {
modal.close();
appContent.removeAttribute('inert'); // 恢复
}
document.getElementById('open-modal-btn').addEventListener('click', openModal);
document.getElementById('close-modal').addEventListener('click', closeModal);
// 点击遮罩关闭(ESC已有默认行为)
modal.addEventListener('click', (e) => {
if (e.target === modal) closeModal();
});
打开弹窗后,即使用户按Tab键,焦点也只会在对话框内部循环(showModal()自动处理了焦点陷阱),背景区域完全被无视。而且屏幕阅读器也只会朗读对话框的内容,不会跑到背景里去。关闭时去掉inert,页面恢复如初。
三、案例二:表单提交时保护区域
另一个典型场景是表单提交中。为了防止重复提交或中途修改,通常会在表单上盖一层半透明遮罩并禁用所有控件。以往需要给每个input和button设disabled,或者阻止事件冒泡。inert提供了一个更干净的方式:把整个表单区域标记为inert。
<form id="checkout-form">
<label>姓名:<input name="name"></label>
<label>地址:<input name="address"></label>
<button type="submit">提交订单</button>
</form>
const form = document.getElementById('checkout-form');
form.addEventListener('submit', async (e) => {
e.preventDefault();
form.setAttribute('inert', '');
try {
await submitOrder(); // 提交逻辑
showSuccess();
} catch (err) {
showError();
} finally {
form.removeAttribute('inert');
}
});
表单提交期间,所有输入框和按钮都无法操作,但不需要手动管理每个控件的disabled状态。而且CSS可以为form[inert]添加遮罩样式(比如一个半透明覆盖层),从视觉上同时给出反馈。注意:inert本身不改变外观,因此视觉提示需要额外处理,通常通过CSS选择器[inert]配合伪元素完成。
四、案例三:多级菜单的非激活层级
如果一个导航菜单包含多级,只有当前展开的那一级应该是可交互的,其他层级的菜单项虽然可见但不能被操作。过去为了达到这个效果,往往要在每一级菜单上动态切换tabindex和pointer-events。改用inert后,只需要在非激活的<ul>上添加属性。
<nav>
<ul>
<li>首页</li>
<li>
产品
<ul inert>
<li><a href="/phone" rel="external nofollow" >手机</a></li>
<li><a href="/laptop" rel="external nofollow" >笔记本</a></li>
</ul>
</li>
<li>关于</li>
</ul>
</nav>
当用户将“产品”项展开时,用JavaScript移除子菜单上的inert,收起时再加回去。这样省去了对每个子链接单独控制的可访问性负担。
五、inert与现有ARIA属性的区别
很多人第一次看到inert会问:它和aria-hidden有什么不同?和disabled有什么区别?
| 特性 | aria-hidden | disabled | inert |
|---|---|---|---|
| 影响交互 | 不影响 | 阻止单个控件交互 | 阻止整个子树交互 |
| 焦点控制 | 不阻止聚焦 | 阻止聚焦 | 阻止聚焦 |
| 无障碍隐藏 | 是 | 否 | 是 |
| 子元素继承 | 是 | 否 | 是 |
aria-hidden只告诉辅助技术“别看这个”,但键盘聚焦仍然可以进去,用户按Tab键会落到一个屏幕阅读器看不见的控件上,造成困惑。disabled只对单个表单控件有效,不能批量禁用一整块区域。inert则同时解决了交互、聚焦和无障碍三方面的问题,是语义上最完备的冻结方案。
六、浏览器支持与渐进增强
inert属性在主流通用浏览器中已经全面可用:
- Chrome 102+ 和 Edge 102+
- Firefox 112+
- Safari 15.5+(桌面和移动端)
这意味着在2024年的今天,绝大多数用户都可以享受到原生inert的好处。对于极少数尚不支持的浏览器,可以使用一个轻量的polyfill:
// 简易polyfill:检测inert支持,不支持时手动模拟
if (!('inert' in HTMLElement.prototype)) {
const observer = new MutationObserver((mutations) => {
// 对新增的[inert]元素递归设置tabindex=-1并添加aria-hidden
// 省略完整实现,实际项目中建议用官方inert-polyfill
});
}
但通常建议直接使用,不支持的浏览器权当退化(模态背景依然可以用传统方式处理)。
七、视觉反馈的补充
inert只负责行为,不处理外观。为了让“冻结”状态对视觉用户可见,一般会配合CSS使用:
[inert] {
opacity: 0.6;
pointer-events: none; /* 进一步保证鼠标穿透 */
user-select: none;
}
/* 如果是模态背景,通常还会加一个覆盖层 */
pointer-events: none在有些场景下并不是必须的,因为inert已经阻止了交互,但加上它可以防止鼠标悬停时出现文本选择或光标变化。
八、总结
inert属性的出现,标志着浏览器开始把“区域冻结”这种常见的交互模式原生化。它不再需要开发者小心翼翼地管理整棵DOM树上每个可聚焦节点的tabindex,也不用担心遗漏某个角落的aria-hidden。一行属性,三个效果——交互屏蔽、焦点管理、无障碍隐藏——全部生效。
如果你的项目里有模态弹窗、分步表单、多级菜单、或者任何需要“暂时冻结一片区域”的场景,现在就可以把inert用起来。它不像其他新API需要大量学习,就是一个属性和一种思路的转变,但能带来代码量的大幅减少和可访问性的显著提升。

