写前端的人每次做抽屉和弹窗都要手写一套 focus trap,今天一个 HTML 属性把它彻底原生化了
写前端的人每次做抽屉和弹窗都要手写一套 focus trap,今天一个 HTML 属性把它彻底原生化了
做过后台管理系统的都踩过这个坑:弹窗一打开,背景居然还能被 Tab 键 focus 进去,用户点一下就穿透了。多年来解决这个问题只能靠自己写一套 focus trap——遍历全页找所有可聚焦元素,监听 Tab/Shift+Tab,循环回来,还要处理 ESC。
这套逻辑写一次 50 行起步,每次换项目还要复制粘贴。
inert 属性把这件事彻底原生化了。
inert 是什么
inert 是 HTML 全局属性,加在任意元素上,这棵 DOM 子树就会被浏览器「冻结」:
- 不能被 focus(Tab 进去会跳过)
- 点击事件全部失效
- 屏幕阅读器完全忽略
- 文字选择也被禁止
效果等于同时设置了 pointer-events: none、user-select: none、aria-hidden="true",但不需要你写任何 CSS 和 aria 属性——浏览器直接把这个区域从无障碍树里摘掉。
写一个 modal 的前后对比
以前:手写 focus trap
function trapFocus(el) {
const focusable = el.querySelectorAll(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
);
const first = focusable[0];
const last = focusable[focusable.length - 1];
function handleTab(e) {
if (e.key !== 'Tab') return;
if (e.shiftKey) {
if (document.activeElement === first) {
e.preventDefault();
last.focus();
}
} else {
if (document.activeElement === last) {
e.preventDefault();
first.focus();
}
}
}
el.addEventListener('keydown', handleTab);
first?.focus();
}
// 打开弹窗时调用
trapFocus(document.getElementById('modal'));
还要在关闭时清理监听器,还要处理 ESC 关闭,还要管滚动锁……一个弹窗光焦点管理就 80 行。
现在:两行搞定
// 打开弹窗时——把背景 inert 掉
document.getElementById('app-root').inert = true;
// 关闭弹窗时
document.getElementById('app-root').inert = false;
背景被 inert 后,Tab 键永远只在弹窗内部循环,不需要任何事件监听,不需要遍历可聚焦元素,不需要处理边界情况。浏览器自己把这个行为做进了平台层。
配合 dialog 元素更干净
如果你用原生 <dialog> 的 showModal() 打开,浏览器已经自动把背景 inert 掉了,根本不需要你手动处理:
const dialog = document.getElementById('my-dialog');
dialog.showModal(); // 背景自动 inert,ESC 自动关闭,焦点自动限制
但如果你的 UI 框架里弹窗不是 <dialog> 元素(比如 React Portal 抽到 body 外面、或者自己写的 Drawer 组件),手动给背景容器加 inert 就是最简洁的方案——比写 focus trap 函数少 95% 的代码。
实际落地怎么用
推荐在一个「根容器」上加 inert,而不是在每个弹窗上单独处理:
// 打开任意遮罩类组件时
document.getElementById('app').inert = true;
// 关闭时
document.getElementById('app').inert = false;
这个模式对所有「需要锁定背景」的 UI 都适用:弹窗、抽屉、Command Palette、确认删除框、Splash 加载页。
配合 dialog.showModal() 使用时,只有当你的框架把 dialog 渲染在主应用容器内部时才需要手动 inert;大多数框架的 Portal 会把 dialog 移到 body 下,这时背景本身就不在你的主容器里了,showModal() 的隐式 inert 已经够用。
浏览器支持
inert 在 2023 年 4 月达到 Baseline Newly Available,Chrome 102+、Firefox 112+、Safari 15.5+ 均已支持。到 2025 年 10 月已达到 Baseline Widely Available,全球兼容性基本没有障碍。
对于还在用旧版 Firefox 的极少数用户,可以用 wicg-inert polyfill,但 2026 年这个 polyfill 已经基本是历史遗留了——主流产品不需要再接。
总结
过去做无障碍弹窗,焦点管理是一个独立的技术债——每个项目都要写,每个前端工程师都要踩坑。
今天只需要两行 JS:
background.inert = true; // 打开弹窗
background.inert = false; // 关闭弹窗
焦点自动锁住,屏幕阅读器自动忽略,Tab 键自动循环。那套 50 行的 focus trap 函数,终于可以删了。
评论区
登录后可评论。