Skip to content

inert 非交互

inert 是一个布尔型全局属性,用于使元素及其所有后代元素变为非交互状态。设置 inert 后,元素虽然在页面上可见,但用户无法通过鼠标、键盘或辅助技术与其中任何内容进行交互。这在模态对话框背景、页面加载中、禁用表单区域等场景下非常实用。

前置知识

阅读本节前,建议先了解:popover 弹出层

基础概念

什么是 inert 属性

inert 属性让元素"存在但不可操作"。与 hidden 不同,inert 元素在页面上仍然可见,但完全阻止交互:

  • 点击、输入、选择等鼠标操作无效
  • Tab 键无法聚焦到其中的元素
  • 屏幕阅读器会跳过这些内容
  • 表单控件不会被提交
html
<!-- 基本用法 -->
<div inert>
  <p>这段文字可见但不可交互。</p>
  <button>这个按钮无法点击</button>
  <input type="text" placeholder="无法输入">
</div>

inert 与其他禁用方式对比

方式可见性鼠标交互键盘聚焦辅助技术适用场景
inert可见全部阻止无法聚焦跳过模态背景、临时禁用
hidden不可见N/AN/A跳过内容不相关
disabled (表单)可见(变灰)阻止跳过提示禁用单个表单控件
pointer-events: none可见阻止点击可聚焦仍可读CSS 层面禁用
aria-disabled="true"可见允许可聚焦提示禁用仅语义标记

语法

html
<!-- 布尔属性:有值即为 true -->
<div inert>内容不可交互</div>
<div inert="">内容不可交互</div>
<div inert="inert">内容不可交互</div>

<!-- 移除 inert 属性恢复交互 -->
<div>内容可交互</div>

JavaScript 操作

javascript
const section = document.getElementById('disabled-section');

// 设置为非交互
section.inert = true;

// 恢复交互
section.inert = false;

// 检查状态
console.log(section.inert); // true 或 false

详细说明

典型应用:模态对话框背景

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>inert 模态背景示例</title>
  <style>
    /* 页面内容 */
    .page-content {
      padding: 20px;
      transition: opacity 0.3s;
    }

    .page-content[dimmed] {
      opacity: 0.5;
      pointer-events: none; /* 额外的视觉反馈 */
    }

    /* 模态对话框 */
    .modal-overlay {
      display: none;
      position: fixed;
      inset: 0;
      background: rgba(0, 0, 0, 0.5);
      align-items: center;
      justify-content: center;
    }

    .modal-overlay.active {
      display: flex;
    }

    .modal-box {
      background: white;
      padding: 32px;
      border-radius: 12px;
      max-width: 400px;
      box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
    }
  </style>
</head>
<body>
  <!-- 页面主体内容 -->
  <main id="main-content" class="page-content">
    <h1>网站首页</h1>
    <nav>
      <a href="#">首页</a>
      <a href="#">关于</a>
      <a href="#">联系</a>
    </nav>
    <form>
      <input type="text" placeholder="搜索...">
      <button type="submit">搜索</button>
    </form>
    <p>这里有很多可交互的内容...</p>
  </main>

  <!-- 模态对话框 -->
  <div class="modal-overlay" id="modal">
    <div class="modal-box" role="dialog" aria-labelledby="modal-title">
      <h2 id="modal-title">确认操作</h2>
      <p>确定要删除这条记录吗?此操作不可撤销。</p>
      <button id="btn-cancel">取消</button>
      <button id="btn-confirm">确认删除</button>
    </div>
  </div>

  <button id="btn-open-modal">打开模态框</button>

  <script>
    const mainContent = document.getElementById('main-content');
    const modal = document.getElementById('modal');

    // 打开模态框:使页面内容变为 inert
    document.getElementById('btn-open-modal').addEventListener('click', () => {
      modal.classList.add('active');
      mainContent.inert = true;    // 关键:禁用背景交互
      mainContent.classList.add('dimmed');
      document.getElementById('btn-confirm').focus();
    });

    // 关闭模态框:恢复页面内容交互
    function closeModal() {
      modal.classList.remove('active');
      mainContent.inert = false;
      mainContent.classList.remove('dimmed');
      document.getElementById('btn-open-modal').focus();
    }

    document.getElementById('btn-cancel').addEventListener('click', closeModal);
    document.getElementById('btn-confirm').addEventListener('click', closeModal);

    // ESC 关闭
    document.addEventListener('keydown', (e) => {
      if (e.key === 'Escape' && modal.classList.contains('active')) {
        closeModal();
      }
    });
  </script>
</body>
</html>

典型应用:分步表单禁用

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>inert 分步表单</title>
  <style>
    .step {
      padding: 20px;
      border: 1px solid #e2e8f0;
      border-radius: 8px;
      margin-bottom: 16px;
    }

    .step[inert] {
      opacity: 0.4;
    }

    .step-header {
      display: flex;
      justify-content: space-between;
      align-items: center;
    }

    .step-number {
      width: 32px;
      height: 32px;
      border-radius: 50%;
      display: flex;
      align-items: center;
      justify-content: center;
      font-weight: 600;
      background: #e2e8f0;
      color: #64748b;
    }

    .step.active .step-number {
      background: #3b82f6;
      color: white;
    }

    .step.done .step-number {
      background: #22c55e;
      color: white;
    }

    label {
      display: block;
      margin-bottom: 8px;
    }

    input {
      width: 100%;
      padding: 8px 12px;
      border: 1px solid #e2e8f0;
      border-radius: 4px;
      margin-bottom: 12px;
    }
  </style>
</head>
<body>
  <h1>用户注册</h1>

  <form id="registration-form">
    <!-- 步骤 1:激活 -->
    <fieldset class="step active" id="step-1">
      <legend>基本信息</legend>
      <label>用户名:<input type="text" name="username" required></label>
      <label>邮箱:<input type="email" name="email" required></label>
      <button type="button" onclick="goToStep(2)">下一步</button>
    </fieldset>

    <!-- 步骤 2:禁用 -->
    <fieldset class="step" id="step-2" inert>
      <legend>详细资料</legend>
      <label>手机号:<input type="tel" name="phone"></label>
      <label>职业:<input type="text" name="occupation"></label>
      <button type="button" onclick="goToStep(1)">上一步</button>
      <button type="button" onclick="goToStep(3)">下一步</button>
    </fieldset>

    <!-- 步骤 3:禁用 -->
    <fieldset class="step" id="step-3" inert>
      <legend>确认提交</legend>
      <div id="summary"></div>
      <button type="button" onclick="goToStep(2)">上一步</button>
      <button type="submit">提交注册</button>
    </fieldset>
  </form>

  <script>
    let currentStep = 1;

    function goToStep(step) {
      // 禁用当前步骤
      const currentEl = document.getElementById(`step-${currentStep}`);
      currentEl.inert = true;
      currentEl.classList.remove('active');

      // 启用目标步骤
      const targetEl = document.getElementById(`step-${step}`);
      targetEl.inert = false;
      targetEl.classList.add('active');

      // 标记已完成的步骤
      for (let i = 1; i < step; i++) {
        document.getElementById(`step-${i}`).classList.add('done');
      }

      currentStep = step;

      // 如果到最后一步,生成摘要
      if (step === 3) {
        const form = document.getElementById('registration-form');
        document.getElementById('summary').innerHTML = `
          <p>用户名:${form.username.value}</p>
          <p>邮箱:${form.email.value}</p>
          <p>手机号:${form.phone.value || '未填写'}</p>
          <p>职业:${form.occupation.value || '未填写'}</p>
        `;
      }
    }
  </script>
</body>
</html>

inert 与 CSS 配合

inert 元素添加视觉反馈,让用户知道这些内容不可交互:

css
/* 为 inert 元素添加视觉反馈 */
[inert] {
  opacity: 0.5;
  cursor: not-allowed;
  filter: grayscale(0.5);
  pointer-events: none; /* 双重保障 */
}

/* 表单区域中的 inert 效果 */
.form-section[inert] {
  position: relative;
}

.form-section[inert]::after {
  content: '请先完成上一步';
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(255, 255, 255, 0.7);
  color: #64748b;
  font-size: 14px;
  border-radius: inherit;
}

inert 对辅助技术的影响

html
<!-- inert 区域对屏幕阅读器的影响 -->
<div inert>
  <!-- 屏幕阅读器会完全跳过这些内容 -->
  <p>这段文字屏幕阅读器不会读出。</p>
  <button>这个按钮不会被识别。</button>
  <a href="#">这个链接不可访问。</a>
</div>

<!-- inert 不影响搜索索引 -->
<!-- 搜索引擎仍然可以索引 inert 元素中的内容 -->

实战示例

页面加载状态

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>inert 加载状态</title>
  <style>
    .page-wrapper {
      position: relative;
      min-height: 100vh;
    }

    .loading-overlay {
      display: none;
      position: fixed;
      inset: 0;
      background: rgba(255, 255, 255, 0.8);
      z-index: 9999;
      align-items: center;
      justify-content: center;
      flex-direction: column;
      gap: 16px;
    }

    .loading-overlay.active {
      display: flex;
    }

    .spinner {
      width: 40px;
      height: 40px;
      border: 4px solid #e2e8f0;
      border-top-color: #3b82f6;
      border-radius: 50%;
      animation: spin 0.8s linear infinite;
    }

    @keyframes spin {
      to { transform: rotate(360deg); }
    }

    [inert] {
      opacity: 0.6;
      pointer-events: none;
    }
  </style>
</head>
<body>
  <div class="loading-overlay" id="loading" role="alert" aria-live="assertive">
    <div class="spinner"></div>
    <p>数据加载中,请稍候...</p>
  </div>

  <div id="app" inert>
    <h1>数据仪表盘</h1>
    <nav>
      <a href="#">概览</a>
      <a href="#">分析</a>
      <a href="#">设置</a>
    </nav>
    <section>
      <table>
        <tr><th>日期</th><th>访问量</th><th>转化率</th></tr>
        <!-- 动态数据行 -->
      </table>
    </section>
  </div>

  <script>
    const app = document.getElementById('app');
    const loading = document.getElementById('loading');

    // 模拟数据加载
    async function loadData() {
      loading.classList.add('active');
      app.inert = true;

      try {
        // 模拟 API 请求
        await new Promise(resolve => setTimeout(resolve, 2000));
        // 处理数据...
      } finally {
        loading.classList.remove('active');
        app.inert = false;
      }
    }

    loadData();
  </script>
</body>
</html>

注意事项

inert 与 disabled 的区别

html
<!-- inert:阻止元素及所有后代的交互 -->
<div inert>
  <input type="text" placeholder="不可输入">  <!-- 不可交互 -->
  <button>不可点击</button>                     <!-- 不可交互 -->
  <a href="#">不可导航</a>                     <!-- 不可交互 -->
  <div>
    <input type="checkbox">                     <!-- 不可交互 -->
  </div>
</div>

<!-- disabled:只对表单控件有效 -->
<fieldset disabled>
  <input type="text" placeholder="变灰不可输入">  <!-- 不可交互 -->
  <button>变灰不可点击</button>                   <!-- 不可交互 -->
  <a href="#">仍然可以点击!</a>                  <!-- 注意:仍可点击 -->
  <div>
    <p>这段文字仍然可以选择和复制。</p>           <!-- 注意:可选择 -->
  </div>
</fieldset>

不要用于 SEO 操控

html
<!-- 错误:不要用 inert 隐藏不想让用户看到的内容 -->
<div inert>
  <p>这段垃圾内容用来堆砌关键词...</p>
</div>

<!-- inert 是功能性的,不是用来欺骗搜索引擎的 -->

浏览器兼容性

浏览器支持版本
Chrome102+
Edge102+
Safari15.5+
Firefox112+

对于旧浏览器的 polyfill:

html
<!-- inert 属性 polyfill -->
<script src="https://unpkg.com/wicg-inert/dist/inert.min.js"></script>

最佳实践

1. 模态对话框优先使用 inert 而非自定义焦点管理

javascript
// 推荐:使用 inert 禁用背景
function openModal() {
  document.getElementById('main-content').inert = true;
  modal.showModal();
}

function closeModal() {
  modal.close();
  document.getElementById('main-content').inert = false;
}

// 不推荐:手动实现焦点陷阱
// function trapFocus(modal) { ... 复杂代码 ... }

2. 为 inert 元素提供视觉反馈

css
/* 始终为 inert 元素提供视觉状态变化 */
[inert] {
  opacity: 0.5;
  pointer-events: none;
  user-select: none;
  filter: saturate(0.3);
}

3. 配合 ARIA 使用

html
<!-- 为使用 inert 的区域添加说明 -->
<div id="page-content" inert aria-hidden="true">
  <!-- 背景内容被禁用 -->
</div>

<div role="dialog" aria-modal="true" aria-labelledby="dialog-title">
  <h2 id="dialog-title">对话框标题</h2>
  <!-- 对话框内容 -->
</div>

下一节

继续学习:鼠标事件

参考链接