Skip to content

hidden 隐藏

hidden 是一个布尔型全局属性,用于指示元素尚未相关不再相关,浏览器不会渲染该元素。与 display: none 不同的是,hidden 具有语义含义,它告诉浏览器和辅助技术:这个元素存在但不应该被显示或被用户感知。

前置知识

阅读本节前,建议先了解:role 与 aria-* 无障碍

基础概念

什么是 hidden 属性

hidden 属性是 HTML5 引入的全局布尔属性,当它存在于元素上时,浏览器会像 display: none 一样不渲染该元素,但同时它还携带语义信息:元素不相关或暂时不需要显示。

html
<!-- 基本用法 -->
<div hidden>
  这段内容不会显示
</div>

<!-- 使用 until-found 值(Chrome 120+) -->
<div hidden="until-found">
  这段内容在通过搜索找到之前不会显示
</div>

hidden 的三种隐藏方式对比

隐藏方式CSS 渲染语义含义辅助技术搜索引擎适用场景
hidden不渲染元素不相关会被跳过不索引条件性隐藏内容
display: none不渲染仅样式控制会被跳过不索引纯视觉隐藏
aria-hidden="true"正常渲染辅助技术隐藏会被跳过可能索引装饰性元素
visibility: hidden占空间不渲染仅样式控制可能读出可能索引保留布局的隐藏

语法

布尔属性用法

html
<!-- 有值和无值等效 -->
<div hidden>隐藏内容</div>
<div hidden="">隐藏内容</div>
<div hidden="hidden">隐藏内容</div>

<!-- hidden="until-found" 适用于搜索时显示 -->
<div hidden="until-found">
  <h2>常见问题解答</h2>
  <p>这部分内容在用户搜索匹配之前保持隐藏。</p>
</div>

通过 CSS 控制

css
/* 所有 hidden 元素默认不显示 */
[hidden] {
  display: none;
}

/* hidden="until-found" 元素显示时高亮 */
[hidden="until-found"]:not(:popover-open) {
  display: none;
}

[hidden="until-found"]:target {
  display: block;
  animation: highlight 1s ease;
}

@keyframes highlight {
  0% { background-color: #fef08a; }
  100% { background-color: transparent; }
}

详细说明

hidden vs display: none

虽然效果类似,但两者有本质区别:

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>hidden vs display:none</title>
  <style>
    /* 错误:不要覆盖 hidden 的默认行为 */
    /* [hidden] { display: block; } */ /* 不要这样做! */

    /* 如果需要让 hidden 元素显示,应该移除 hidden 属性 */
    .visible {
      /* hidden 元素需要显示时,移除 hidden 属性而非修改样式 */
    }

    /* display: none 是纯样式,可以随时通过 CSS 切换 */
    .css-hidden {
      display: none;
    }
    .css-visible {
      display: block;
    }
  </style>
</head>
<body>
  <!-- 语义隐藏:内容暂时不相关 -->
  <div id="success-msg" hidden>
    <p>操作成功!您的数据已保存。</p>
  </div>

  <!-- 样式隐藏:仅用于视觉切换 -->
  <div id="toggle-panel" class="css-hidden">
    <p>这是可以切换显示/隐藏的面板。</p>
  </div>

  <script>
    // 显示 hidden 元素:移除 hidden 属性
    function showSuccess() {
      document.getElementById('success-msg').hidden = false;
    }

    // 隐藏 hidden 元素:添加 hidden 属性
    function hideSuccess() {
      document.getElementById('success-msg').hidden = true;
    }

    // CSS 隐藏:切换类名
    function togglePanel() {
      const panel = document.getElementById('toggle-panel');
      panel.classList.toggle('css-hidden');
      panel.classList.toggle('css-visible');
    }
  </script>
</body>
</html>

hidden vs aria-hidden

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>hidden vs aria-hidden</title>
  <style>
    .decorative {
      position: absolute;
      width: 100px;
      height: 100px;
      background: linear-gradient(135deg, #667eea, #764ba2);
      border-radius: 50%;
    }
  </style>
</head>
<body>
  <!-- hidden:元素完全不渲染,所有用户都无法感知 -->
  <div hidden>
    <p>这段文字所有人都看不到,包括屏幕阅读器用户。</p>
  </div>

  <!-- aria-hidden="true":元素视觉可见但辅助技术会跳过 -->
  <div aria-hidden="true">
    <span class="decorative"></span>
    <!-- 装饰性图标,屏幕阅读器不需要读出 -->
    <svg aria-hidden="true" width="24" height="24" viewBox="0 0 24 24">
      <path d="M12 2L2 7l10 5 10-5-10-5z"/>
    </svg>
    <!-- 视觉可见的装饰,但屏幕阅读器不读 -->
  </div>

  <!-- 不要对可见的交互元素使用 aria-hidden -->
  <!-- 错误示例 -->
  <!-- <button aria-hidden="true">点击我</button> -->
</body>
</html>

何时使用 hidden

推荐使用 hidden 的场景:

html
<!-- 1. 条件性内容 - 根据用户状态显示不同内容 -->
<div id="login-prompt">
  <p>请先登录查看内容。</p>
</div>
<div id="user-content" hidden>
  <h2>欢迎回来,用户!</h2>
  <p>这是登录后才能看到的内容。</p>
</div>

<!-- 2. 表单步骤 - 多步骤表单中隐藏未激活的步骤 -->
<form id="multi-step-form">
  <fieldset id="step-1">
    <legend>第一步:基本信息</legend>
    <!-- 表单字段 -->
  </fieldset>

  <fieldset id="step-2" hidden>
    <legend>第二步:详细资料</legend>
    <!-- 表单字段 -->
  </fieldset>

  <fieldset id="step-3" hidden>
    <legend>第三步:确认提交</legend>
    <!-- 表单字段 -->
  </fieldset>
</form>

<!-- 3. 消息提示 - 操作后的反馈信息 -->
<div id="toast-success" hidden role="alert">
  操作成功完成!
</div>
<div id="toast-error" hidden role="alert">
  操作失败,请重试。
</div>

<!-- 4. 搜索结果面板 -->
<div id="search-results" hidden>
  <h2>搜索结果</h2>
  <ul id="results-list"></ul>
</div>

<!-- 5. 对话框 - 使用 hidden 而非 display:none -->
<dialog id="my-dialog" hidden>
  <h3>确认操作</h3>
  <p>确定要执行此操作吗?</p>
  <button onclick="this.closest('dialog').close()">取消</button>
  <button onclick="this.closest('dialog').close()">确定</button>
</dialog>

不适合使用 hidden 的场景:

html
<!-- 1. CSS 动画过渡 - 需要 hidden 到 visible 的动画效果 -->
<!-- 使用 opacity + visibility + pointer-events 代替 -->
<style>
  .fade-enter {
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.3s, visibility 0.3s;
  }
  .fade-enter.active {
    opacity: 1;
    visibility: visible;
  }
</style>

<!-- 2. 折叠面板 - 需要高度过渡动画 -->
<!-- 使用 max-height + overflow 代替 -->
<style>
  .collapsible-content {
    max-height: 0;
    overflow: hidden;
    transition: max-height 0.3s ease;
  }
  .collapsible-content.open {
    max-height: 500px;
  }
</style>

<!-- 3. 标签页面板 - 需要键盘焦点管理 -->
<!-- 使用 aria-selected + display 控制代替 -->

hidden="until-found"

hidden="until-found" 是一个较新的值(Chrome 120+),允许内容在用户通过浏览器搜索(Ctrl+F / Cmd+F)找到匹配文本时自动显示:

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>hidden=until-found 示例</title>
  <style>
    /* 页面导航 - 折叠的章节 */
    .chapter-content {
      padding: 20px;
      border: 1px solid #e2e8f0;
      border-radius: 8px;
      margin-bottom: 10px;
    }

    /* until-found 隐藏的内容被找到时会高亮 */
    :target {
      animation: flash-highlight 2s ease;
    }

    @keyframes flash-highlight {
      0%, 100% { background-color: transparent; }
      50% { background-color: #fef08a; }
    }
  </style>
</head>
<body>
  <h1>产品文档</h1>

  <!-- 目录 -->
  <nav>
    <a href="#ch1">第一章 安装</a>
    <a href="#ch2">第二章 配置</a>
    <a href="#ch3">第三章 部署</a>
  </nav>

  <!-- 使用 hidden="until-found" 折叠章节 -->
  <section id="ch1" class="chapter" hidden="until-found">
    <h2>第一章:安装指南</h2>
    <p>请按照以下步骤安装应用程序...</p>
    <p>包含环境变量配置、依赖安装等关键信息。</p>
  </section>

  <section id="ch2" class="chapter" hidden="until-found">
    <h2>第二章:配置详解</h2>
    <p>配置文件位于 /etc/app/config.yaml...</p>
    <p>包含数据库连接、缓存配置等详细说明。</p>
  </section>

  <section id="ch3" class="chapter" hidden="until-found">
    <h2>第三章:部署流程</h2>
    <p>使用 Docker 部署的步骤如下...</p>
    <p>包含负载均衡、健康检查等生产环境配置。</p>
  </section>

  <script>
    // 监听 hidden 元素变为可见
    document.querySelectorAll('[hidden="until-found"]').forEach(section => {
      const observer = new MutationObserver(() => {
        if (!section.hidden) {
          console.log(`章节 ${section.id} 因搜索匹配而被展开`);
        }
      });
      observer.observe(section, { attributes: true, attributeFilter: ['hidden'] });
    });
  </script>
</body>
</html>

实战示例

多步骤表单

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

    [hidden] {
      display: none;
    }

    .progress-bar {
      display: flex;
      gap: 8px;
      margin-bottom: 20px;
    }

    .progress-step {
      flex: 1;
      height: 4px;
      background: #e2e8f0;
      border-radius: 2px;
    }

    .progress-step.active {
      background: #3b82f6;
    }

    .progress-step.done {
      background: #22c55e;
    }
  </style>
</head>
<body>
  <h1>用户注册</h1>

  <!-- 进度指示 -->
  <div class="progress-bar">
    <div class="progress-step active" data-step="1"></div>
    <div class="progress-step" data-step="2"></div>
    <div class="progress-step" data-step="3"></div>
  </div>

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

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

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

  <script>
    let currentStep = 1;

    function showStep(step) {
      // 隐藏所有步骤
      document.querySelectorAll('.form-step').forEach(el => {
        el.hidden = true;
      });

      // 显示目标步骤
      document.getElementById(`step-${step}`).hidden = false;

      // 更新进度条
      document.querySelectorAll('.progress-step').forEach(el => {
        const s = Number(el.dataset.step);
        el.classList.toggle('active', s === step);
        el.classList.toggle('done', s < step);
      });

      currentStep = step;
    }

    function nextStep(step) {
      showStep(step);
      if (step === 3) {
        // 生成摘要
        const form = document.getElementById('register-form');
        const summary = document.getElementById('summary');
        summary.innerHTML = `
          <p>用户名:${form.username.value}</p>
          <p>邮箱:${form.email.value}</p>
          <p>手机号:${form.phone.value || '未填写'}</p>
          <p>职业:${form.occupation.value || '未填写'}</p>
        `;
      }
    }

    function prevStep(step) {
      showStep(step);
    }
  </script>
</body>
</html>

注意事项

不要用 CSS 覆盖 hidden

css
/* 危险:不要这样做!这会破坏 hidden 的语义 */
[hidden] {
  display: block !important;
}

/* 正确做法:需要显示时,移除 hidden 属性 */
element.hidden = false;
// 或
element.removeAttribute('hidden');

表单中的 hidden 输入

html
<!-- 注意:<input type="hidden"> 和 hidden 属性不同 -->
<!-- type="hidden" 是表单隐藏字段,会随表单提交 -->
<input type="hidden" name="token" value="abc123">

<!-- hidden 属性是隐藏整个元素 -->
<div hidden>
  <input name="field" value="value">
  <!-- 被隐藏的表单字段不会随表单提交 -->
</div>

hidden 不等于 inert

html
<!-- hidden:完全不可见、不可交互 -->
<div hidden>
  <button>这个按钮不可见也不可点击</button>
</div>

<!-- inert:可见但不可交互 -->
<div inert>
  <button>这个按钮可见但不可点击</button>
  <p>这段文字可见但不可选择</p>
</div>

最佳实践

1. 优先使用 hidden 而非 display: none

当元素需要条件性显示/隐藏时,优先使用 hidden 属性,因为它携带语义:

javascript
// 推荐:使用 hidden 属性
element.hidden = true;   // 隐藏
element.hidden = false;  // 显示

// 不推荐:操作 style
element.style.display = 'none';  // 隐藏
element.style.display = '';      // 显示

2. 结合 ARIA 属性

html
<!-- 使用 hidden 隐藏时,配合 aria-live 实现动态通知 -->
<div id="notification" hidden aria-live="polite"></div>

<script>
  function showNotification(message) {
    const el = document.getElementById('notification');
    el.textContent = message;
    el.hidden = false;

    // 3 秒后自动隐藏
    setTimeout(() => {
      el.hidden = true;
    }, 3000);
  }
</script>

3. 过渡动画的处理

当需要显示/隐藏过渡动画时,使用 requestAnimationFrame 在移除 hidden 后添加动画类:

javascript
function showWithAnimation(element) {
  element.hidden = false;
  // 触发重排后添加动画类
  requestAnimationFrame(() => {
    element.classList.add('fade-in');
  });
}

function hideWithAnimation(element) {
  element.classList.add('fade-out');
  element.addEventListener('animationend', () => {
    element.hidden = true;
    element.classList.remove('fade-out', 'fade-in');
  }, { once: true });
}

下一节

继续学习:translate 翻译控制

参考链接