用 inert 与 dialog 提升模态框可访问性

举报
yd_217513249 发表于 2026/10/26 00:12:50 2026/10/26
【摘要】 介绍如何用原生 dialog 的 showModal() 打开模态框,并对主内容区设置 inert,阻止键盘焦点与鼠标交互逃逸到背景;同时处理关闭后的焦点返回与浏览器降级方案,给出可运行的最小示例。

用 inert 属性在模态框打开时禁用背景交互

问题场景

用 <dialog> 元素配合 showModal() 打开模态框时,浏览器会把对话框提升到顶层,并默认把焦点限制在对话框内。但很多页面会把对话框写在主内容容器内部,或者用自定义 div 模拟模态框,结果按 Tab 键时焦点仍然可能跑到背景的按钮和链接上;屏幕阅读器也能继续读取背景内容。用户容易在模态框未关闭时误点背景操作,造成流程混乱。

inert 属性可以标记一棵 DOM 子树为“惰性”:该子树不可点击、不可聚焦,并从可访问性树中移除。把它用在模态框打开期间的主内容区,可以补上 showModal() 之外的背景交互隔离。

最小可运行示例

以下示例把主内容放在 #main 中,把原生 dialog 放在与 #main 并列的位置。打开时给 #main 设置 inert,关闭时移除,并把焦点还给触发按钮。

<!doctype html>
<html lang='zh-CN'>
<head>
  <meta charset='utf-8'>
  <title>inert 模态框示例</title>
  <style>
    body { font-family: sans-serif; padding: 2rem; }
    #main { max-width: 40rem; }
    dialog {
      border: 1px solid #999;
      border-radius: 8px;
      padding: 1.5rem;
      max-width: 28rem;
    }
    dialog::backdrop { background: rgba(0, 0, 0, .5); }
  </style>
</head>
<body>
  <main id='main'>
    <h1>页面主内容</h1>
    <p>模态框打开后,这里会设置 inert,键盘焦点不会逃逸到这里。</p>
    <button id='openBtn' type='button'>打开模态框</button>
    <a href='https://example.com' target='_blank' rel='noreferrer'>示例链接</a>
  </main>

  <dialog id='myDialog' aria-labelledby='dialogTitle' aria-describedby='dialogDesc'>
    <h2 id='dialogTitle'>确认操作</h2>
    <p id='dialogDesc'>请确认是否继续。按 Esc 或点击取消可关闭。</p>
    <form method='dialog'>
      <button value='cancel' type='submit'>取消</button>
      <button value='confirm' type='submit'>确认</button>
    </form>
  </dialog>

  <script>
    const main = document.querySelector('#main');
    const dialog = document.querySelector('#myDialog');
    const openBtn = document.querySelector('#openBtn');
    let lastFocused = null;

    function openDialog() {
      lastFocused = document.activeElement;
      main.setAttribute('inert', '');
      dialog.showModal();
    }

    function closeDialog() {
      dialog.close();
    }

    openBtn.addEventListener('click', openDialog);

    dialog.addEventListener('close', () => {
      main.removeAttribute('inert');
      if (lastFocused && typeof lastFocused.focus === 'function' && lastFocused.isConnected) {
        lastFocused.focus();
      }
    });

    dialog.addEventListener('cancel', (event) => {
      event.preventDefault();
      closeDialog();
    });
  </script>
</body>
</html>

运行方法

把代码保存为 modal-inert.html,用支持 dialog 和 inert 的现代浏览器直接打开。若浏览器限制本地文件的部分行为,可在文件所在目录执行:

python3 -m http.server 8000

然后访问 http://localhost:8000/modal-inert.html。用 Tab 和 Shift+Tab 在页面中移动,观察打开对话框后焦点是否只在对话框的“取消”和“确认”按钮之间循环;按 Esc 关闭后,焦点应回到“打开模态框”按钮。

关键细节与边界

  1. dialog 不要放在 inert 容器内。示例中 #main 会被设置 inert,如果 dialog 也是 #main 的子元素,它自身也会变成惰性,按钮点不了、表单也提交不了。正确做法是让 dialog 与主内容平级,或者打开前把它移动到 body 下。

  2. 焦点返回要处理目标消失的情况。记录 document.activeElement 后,关闭时先用 isConnected 判断原元素是否还在文档中;如果已经被移除,可以把焦点交给一个稳定的容器或返回按钮,而不是强行 focus() 一个脱离文档的节点。

  3. 不要用 aria-hidden 代替 inert。对包含活动焦点的元素设置 aria-hidden,会导致辅助技术忽略该元素,但键盘焦点还在里面,形成冲突。inert 同时处理交互和可访问性树,更适合背景隔离。若必须支持不支持 inert 的旧环境,需要配合 tabindex=-1、事件拦截和 aria-hidden 做降级,并仔细管理焦点。

  4. 浏览器支持与特性检测。inert 在现代浏览器中可以直接使用,但项目若需要覆盖较旧的运行时,应在代码中检测:

    const supportsInert = 'inert' in HTMLElement.prototype;
    

    不支持时走降级分支,不要直接假设属性一定生效。

  5. 滚动锁定是另一件事。dialog::backdrop 能拦住点击背景,但页面滚动条可能仍然可用。若产品要求背景完全静止,可增加 CSS,例如在打开时给 body 加类,设置 overflow: hidden,关闭时移除。

  6. 嵌套对话框要分层管理。如果从当前对话框再打开第二个对话框,两个对话框都使用 showModal() 时,浏览器会把顶层对话框作为焦点范围。此时不建议对下层对话框也设置 inert,否则可能影响返回时的焦点恢复;应分别记录每一层的触发元素,并在对应 close 事件中恢复。

  7. 服务端渲染与框架场景。在使用前端框架时,inert 可以直接作为布尔属性绑定。以常见模板语法为例,打开状态为 isOpen 时,把 inert 绑定到主内容容器,关闭时移除;同时确保对话框节点渲染在 inert 范围之外,避免框架的组件嵌套把对话框包进主内容容器。

小结

showModal() 负责把对话框提升到顶层并建立基础焦点陷阱,inert 负责把背景主内容区从交互和可访问性树中隔离。两者配合,再加上关闭后的焦点返回,可以显著减少键盘焦点逃逸和误操作。落地时重点检查三点:对话框节点是否在 inert 范围之外、焦点返回目标是否仍然有效、旧浏览器是否有降级路径。

【版权声明】本文为华为云社区用户原创内容,未经允许不得转载,如需转载请自行联系原作者进行授权。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。