在前端开发中,构建模态对话框、侧边菜单或多步骤向导时,我们经常需要让页面的某一部分变得“不可交互”——不仅用户无法点击或聚焦,屏幕阅读器也应该忽略它们。过去,我们不得不结合 aria-hidden、手动禁用焦点以及复杂的 JavaScript 焦点陷阱来实现这些效果。现在,HTML 引入了一个原生的 inert 属性,一行标记即可将整个元素子树冻结,彻底简化了可访问交互组件的开发。本文将从基础概念出发,通过三个完整的实战案例,带你全方位掌握 inert 属性的强大能力。
一、认识 inert 属性:不仅仅是“禁用”
inert 是一个布尔 HTML 属性,可以添加到任何元素上。当元素具有 inert 属性时,该元素及其所有后代都会被浏览器视为“惰性”状态,具体表现为:
- 无法聚焦:任何可聚焦元素(链接、按钮、输入框等)都不能通过鼠标点击、Tab 键或 JavaScript
focus()获得焦点。 - 点击事件被阻止:浏览器不会触发与用户点击相关的任何事件,就像元素被完全覆盖了一个透明屏障。
- 辅助技术忽略:屏幕阅读器会忽略 inert 子树中的所有内容,相当于自动应用了
aria-hidden="true"。 - 文本选中仍可能:尽管交互被阻止,但在某些浏览器中文本选择可能仍然可用,不过焦点和操作完全失效。
这一行为与 hidden 属性不同,hidden 是从渲染树中移除元素,而 inert 保留了元素的视觉存在,仅使其在功能上“不可用”。相比过去手动设置 disabled、aria-hidden 和 tabindex="-1" 的组合,inert 提供了一个简单可靠的单一入口。
1.1 浏览器支持
截至 2025 年初,inert 属性已在 Chrome 102+、Edge 102+、Firefox 112+、Safari 15.5+ 中获得原生支持,覆盖了绝大多数现代浏览器。对于不支持的旧版浏览器,可以使用 WICG 的 inert polyfill 来实现相同效果。
1.2 基本语法
<!-- 直接在HTML中添加inert属性 -->
<div inert>
<button>这个按钮无法点击</button>
<a href="#" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" >这个链接也无法访问</a>
<input type="text"><!-- 无法聚焦 -->
</div>
<!-- 通过JavaScript动态设置 -->
document.getElementById('sidebar').inert = true;
一旦设置,即使通过开发者工具强制聚焦也无法使惰性元素获得焦点,浏览器在底层保证了这一行为的可靠性。
二、inert 与 aria-hidden 的本质区别
许多开发者已经习惯了使用 aria-hidden="true" 来隐藏装饰性元素,但在需要阻止交互的场景中,aria-hidden 存在明显的不足:
- 无法阻止键盘焦点:即使
aria-hidden="true",用户仍然可以通过 Tab 键聚焦到被隐藏区域内的按钮或输入框,导致焦点“消失”在屏幕阅读器的视野之外。 - 无法阻止鼠标点击:
aria-hidden只影响辅助技术,视觉上按钮仍然可以被点击,这对于遮挡在模态框后面的内容来说是个问题。 - 需要额外配合:通常还需要遍历所有可聚焦元素,手动添加
tabindex="-1"或禁用它们,逻辑分散且容易遗漏。
而 inert 从浏览器层面同时解决了焦点、点击和辅助技术三方面的问题,成为“使某个区域不可用”的标准做法。下面的对比表能帮助快速理解:
特性对比:
inert aria-hidden hidden
视觉隐藏 ✗ ✗ ✓
阻止聚焦 ✓ ✗ ✓
阻止点击 ✓ ✗ ✓
阻止辅助技术 ✓ ✓ ✓
适用于侧边菜单 ✓ ✗ ✗
因此,当需要保留元素在布局中的视觉空间但使其不可交互时,inert 是目前最正确的选择。
三、实战案例一:使用 inert 构建可访问的模态对话框
模态对话框是最常见的需要“背景冻结”的场景。使用 inert,我们不再需要复杂的焦点陷阱脚本,只需在打开模态时将背景内容设置为 inert,关闭时移除。
3.1 结构设计
<div id="main-content">
<h1>页面主内容</h1>
<p>这里是主要的页面内容区域。</p>
<button id="open-modal">打开模态框</button>
<a href="#" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" >一个普通链接</a>
</div>
<div id="modal-overlay" role="dialog" aria-modal="true" aria-labelledby="modal-title" hidden>
<div id="modal-dialog">
<h2 id="modal-title">确认操作</h2>
<p>您确定要执行此操作吗?</p>
<button id="confirm-btn">确认</button>
<button id="cancel-btn">取消</button>
</div>
</div>
3.2 JavaScript 控制逻辑
const mainContent = document.getElementById('main-content');
const modalOverlay = document.getElementById('modal-overlay');
const openBtn = document.getElementById('open-modal');
const cancelBtn = document.getElementById('cancel-btn');
const confirmBtn = document.getElementById('confirm-btn');
let returnFocusEl = null;
function openModal() {
// 将背景内容设为惰性,使其不可交互且被辅助技术忽略
mainContent.inert = true;
// 显示模态层
modalOverlay.hidden = false;
// 记录当前焦点元素,以便关闭后恢复
returnFocusEl = document.activeElement;
// 将焦点移入模态内部第一个可聚焦元素
document.getElementById('confirm-btn').focus();
}
function closeModal() {
mainContent.inert = false;
modalOverlay.hidden = true;
// 焦点返回到触发按钮
if (returnFocusEl) {
returnFocusEl.focus();
}
}
openBtn.addEventListener('click', openModal);
cancelBtn.addEventListener('click', closeModal);
confirmBtn.addEventListener('click', () => {
alert('操作已确认');
closeModal();
});
// 按 Esc 键关闭
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && !modalOverlay.hidden) {
closeModal();
}
});
这个方案的亮点在于:打开模态时,整个背景区域自动冻结。用户无论如何按 Tab 键,焦点都不会逃离模态框内部的可聚焦元素,因为背景区域的所有元素已经无法被聚焦。不需要像过去那样编写复杂的 querySelectorAll 遍历或焦点陷阱循环。同时,屏幕阅读器用户在浏览页面时,也无法进入背景内容,体验与视觉用户完全一致。
3.3 与原生 <dialog> 的配合
如果你使用 <dialog> 元素的 showModal() 方法,浏览器已经为背景提供了类似 inert 的效果(通过顶层渲染机制)。但在自定义 UI 框架或需要更多样式控制时,上面的 inert 方案提供了完全的控制权。此外,inert 同样可以应用于 <dialog> 外的任何自定义内容,例如嵌入在页面中的模态面板。
四、实战案例二:侧边滑出菜单与背景冻结
在移动端或管理后台,侧边菜单经常从屏幕边缘滑出,同时背景内容需要被“半透明遮挡”且不可操作。使用 inert 可以极其简单地实现这一行为。
<header>
<button id="menu-toggle">☰ 菜单</button>
</header>
<main id="main-content">
<p>主内容区域</p>
<a href="#" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" >其他链接</a>
</main>
<aside id="sidebar" hidden>
<nav>
<ul>
<li><a href="#" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" >首页</a></li>
<li><a href="#" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" >产品</a></li>
<li><a href="#" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" >关于我们</a></li>
</ul>
</nav>
<button id="close-menu">关闭菜单</button>
</aside>
const main = document.getElementById('main-content');
const sidebar = document.getElementById('sidebar');
const toggleBtn = document.getElementById('menu-toggle');
const closeBtn = document.getElementById('close-menu');
function openSidebar() {
main.inert = true;
sidebar.hidden = false;
// 将焦点移动到菜单内的第一个链接
sidebar.querySelector('a').focus();
}
function closeSidebar() {
main.inert = false;
sidebar.hidden = true;
toggleBtn.focus();
}
toggleBtn.addEventListener('click', openSidebar);
closeBtn.addEventListener('click', closeSidebar);
当侧边菜单打开时,主内容区域自动变为 inert。用户无法通过 Tab 键或点击意外触发主内容中的任何操作,所有交互自然集中在侧边菜单中。当菜单关闭时,背景恢复正常,焦点也自动返回到菜单触发按钮。代码量极少,而且完全不需要额外处理 tabindex 或事件拦截。
五、实战案例三:多步骤表单向导中的步骤冻结
多步骤表单(如注册向导、结账流程)通常将流程拆分为多个步骤页面,当前未激活的步骤区域虽然可见,但不应该被用户直接操作。使用 inert 可以完美地将未激活的步骤冻结,而不需要将它们用 hidden 完全隐藏(这样能保持视觉连贯性)。
<form id="wizard">
<div class="step" id="step1">
<h3>第一步:个人信息</h3>
<label>姓名:<input type="text" name="name"></label>
<label>邮箱:<input type="email" name="email"></label>
<button type="button" class="next-step">下一步</button>
</div>
<div class="step" id="step2" inert>
<h3>第二步:地址信息</h3>
<label>城市:<input type="text" name="city"></label>
<label>街道:<input type="text" name="street"></label>
<button type="button" class="prev-step">上一步</button>
<button type="button" class="next-step">下一步</button>
</div>
<div class="step" id="step3" inert>
<h3>第三步:确认并提交</h3>
<p>请检查信息无误后提交。</p>
<button type="button" class="prev-step">上一步</button>
<button type="submit">提交</button>
</div>
</form>
let currentStep = 1;
const totalSteps = 3;
document.querySelectorAll('.next-step').forEach(btn => {
btn.addEventListener('click', () => {
if (currentStep {
btn.addEventListener('click', () => {
if (currentStep > 1) {
document.getElementById(`step${currentStep}`).inert = true;
currentStep--;
const prevStepEl = document.getElementById(`step${currentStep}`);
prevStepEl.inert = false;
prevStepEl.querySelector('input')?.focus();
}
});
});
该方案使每一步骤在被冻结后完全不可交互,但依然在页面上可见,让用户清晰了解整个流程的进度。同时,由于 inert 自动阻止了键盘和鼠标事件,底部已经完成的步骤不会干扰当前步骤的表单验证或输入。
六、进阶技巧:inert 与动画过渡的结合
inert 属性可以随时通过 JavaScript 切换,配合 CSS 过渡动画可以创造出流畅的体验。例如,当侧边菜单滑出时,先将菜单内容设为 inert="false" 并启动滑出动画,动画结束后再将背景内容设为 inert="true"。反之,在滑回动画开始前先将背景的 inert 移除,确保动画期间背景仍不可交互。这样可以防止在过渡期间用户误操作。
// 打开时
function openWithAnimation() {
sidebar.hidden = false;
// 短暂延迟后设置背景 inert,等待动画开始
setTimeout(() => {
main.inert = true;
}, 50);
sidebar.querySelector('a').focus();
}
// 关闭时先移除背景 inert
function closeWithAnimation() {
main.inert = false;
// 动画结束后隐藏
sidebar.addEventListener('transitionend', () => {
sidebar.hidden = true;
}, { once: true });
}
这种微妙的控制让整体体验更加专业。
七、注意事项与最佳实践
- 不要滥用:将过多的页面区域设为 inert 可能导致用户混淆。通常只在模态、菜单打开等明确的临时状态下使用。
- 保持可发现性:确保 inert 区域的视觉状态有明显的样式提示(如半透明遮罩、变灰等),否则用户可能无法理解为何无法交互。
- 配合
aria-modal或aria-hidden:虽然inert已经处理了大部分可访问性,但在复杂场景中,显式添加aria-hidden可以提供双重保障,尤其是对于尚未完全支持inert的辅助技术。 - 焦点管理:虽然
inert防止了焦点逃逸,但打开模态或菜单时,仍需手动将焦点移入激活区域,并在关闭后恢复焦点,这是良好无障碍体验的核心。 - 动态内容:如果惰性区域内部通过 JavaScript 动态创建了新的可聚焦元素,它们仍然继承父容器的 inert 状态,行为保持一致。
八、总结
inert 属性是 HTML 平台在可访问性和交互控制方面的一项重大改进。它用一个简单的属性消除了过去需要大量 JavaScript 精心编排的冻结效果,使得模态对话框、侧边菜单、分步表单等组件的开发变得事半功倍。通过本文的三个实战案例,你可以看到 inert 如何无缝地融入现有工作流,代替以往的 aria-hidden 和手动焦点遍历。
下一次当你需要暂时“禁用”页面的一部分时,不妨先想想:是不是加一个 inert 就能解决?拥抱这个原生特性,你的代码将更简洁,你的用户也将获得更一致、更可靠的无障碍体验。

