返回

如何用JS准确检测hidden='until-found'浏览器支持?

javascript

如何检测浏览器对 hidden="until-found" 的支持

咱们在写网页的时候,有时会想用一些比较新的 HTML 特性,比如 hidden="until-found"。这个属性值挺有意思,它允许内容默认隐藏,但在页面内查找(Ctrl+F)或者通过 URL 片段导航 (#) 指向这部分内容时,浏览器会自动把它显示出来。这对于折叠长篇内容、FAQ 列表等场景可能很有用。

问题来了:hidden="until-found" 目前还是个实验性功能(写这篇文章的时候,Firefox 支持得就比较慢),直接用的话,在不支持的浏览器里表现可能就不对了。我们希望的是,只有在浏览器明确支持这个新特性的时候才启用它,否则就用老办法。

你可能第一时间想到去搜怎么检测 HTML 属性的支持情况,但找到的大部分方法,比如用 JavaScript 检查某个元素实例上是否存在 hidden 属性 ('hidden' in element),这次帮不上忙。为啥?

问题在哪?

这里的麻烦在于,hidden 这个属性本身早就存在了,而且用得很广泛。所有现代浏览器都认识 hidden 属性。

关键的区别在于 hidden 属性的

  1. 传统 hidden: 不管你是写 <div hidden><div hidden="hidden"> 还是 <div hidden="任意值"> (甚至 hidden=""),只要 hidden 属性存在,元素就会被隐藏(通常是应用 display: none;)。
  2. 新的 hidden="until-found":
    • 支持 这个新值的浏览器里,元素也会被隐藏,但用的是一种不同的机制(更像是 content-visibility: hidden;),并且它能响应页面内查找和片段导航。
    • 不支持 这个新值的浏览器里,它会把 hidden="until-found" 当作上面第一种情况处理,直接应用 display: none;,并且不会有任何特殊的查找或导航行为。

所以,简单地检查 element.hasAttribute('hidden') 或者 'hidden' in element.style 是不够的。我们需要一种方法,能精确区分浏览器是真的理解 "until-found" 这个值的特殊含义,还是仅仅把它当作一个普通的、让元素隐藏的信号。

分析原因

要找出检测方法,就得先搞清楚支持和不支持的浏览器在处理 hidden="until-found" 时,底层到底有什么本质区别。

根据 MDN 文档和相关规范的说明,主要差异在于应用到元素上的 CSS 样式:

  • 支持 hidden="until-found" 的浏览器: 会给元素应用类似于 content-visibility: hidden; 的样式。这个 CSS 属性会让元素内容不被渲染(跳过绘制和命中测试),但元素本身仍然保留其在布局中的空间(不像 display: none; 那样彻底消失)。这就是为什么页面内查找能“找到”它并让浏览器显示出来的原因。
  • 不支持 hidden="until-found" 的浏览器: 由于不认识 "until-found" 这个特定的值,它会回退到 hidden 属性的标准行为,也就是给元素应用 display: none;display: none; 会将元素从渲染树和布局树中完全移除,它不占据任何空间,也无法被页面内查找发现。

这个差异——content-visibility: hidden vs display: none——就是我们可以利用的突破口!我们可以通过检查一个设置了 hidden="until-found" 的元素最终计算得到的 displaycontent-visibility 样式,来判断浏览器是否真的支持这个特性。

可行的解决方案

基于上面的分析,最靠谱的方案就是通过 JavaScript 创建一个测试元素,给它设置 hidden="until-found",然后检查它的计算样式(Computed Style)。

方案一:检查计算样式

这是目前最直接也最准确的方法。

原理

创建一个临时的 HTML 元素(比如 div),将它的 hidden 属性设置为 "until-found"。为了让浏览器计算它的最终样式,需要(非常短暂地)将这个元素添加到 DOM 中。然后,使用 window.getComputedStyle() 获取这个元素的计算样式。

  • 如果浏览器支持 hidden="until-found",计算样式中的 content-visibility 属性应该会是 hidden(或者类似的值,具体看浏览器实现),而 display 属性 应该是 none (它可能保持默认的 blockinline 等,取决于元素类型)。
  • 如果浏览器不支持 hidden="until-found",它会应用 display: none;。此时检查 display 属性就会得到 none

因此,检查计算得到的 display 样式是否为 none 是一个相对简单的判断依据。

代码示例

下面是一个 JavaScript 函数,用来执行这个检测:

function supportsHiddenUntilFound() {
  // 1. 创建一个测试用的元素
  const testElement = document.createElement('div');

  // 2. 设置 hidden="until-found"
  testElement.setAttribute('hidden', 'until-found');

  // 3. 临时添加到 DOM 以计算样式
  //    为了避免页面闪烁或布局抖动,可以设置一些样式让它不可见且不占空间
  testElement.style.position = 'absolute';
  testElement.style.visibility = 'hidden';
  testElement.style.top = '-9999px';
  testElement.style.left = '-9999px';

  //    注意:必须添加到 document.body (或其他已连接的节点) 才能获取到 *准确* 的计算样式
  document.body.appendChild(testElement);

  let support = false;
  try {
    // 4. 获取计算样式
    const styles = window.getComputedStyle(testElement);

    // 5. 判断:如果 display 不是 none,说明浏览器可能特殊处理了 hidden="until-found"
    //    更严谨的做法可能是检查 content-visibility,但检查 display 是否为 none 更简单通用
    if (styles.display !== 'none') {
      // 为了更确定,可以再检查一下 content-visibility (如果浏览器支持的话)
      // 但仅检查 display !== 'none' 已经能在多数情况下区分了
      // console.log('Computed styles:', styles.display, styles.contentVisibility);
      support = true;
      // 对于一些早期实现或特殊情况,可能 display 仍是 none 但 content-visibility 是 hidden。
      // 如果遇到这种情况,可能需要调整判断逻辑,比如优先检查 content-visibility == 'hidden'。
      // 目前基于 Chrome 的实现,display !== 'none' 是有效的。
    } else {
      // 如果 display 是 none,那基本可以确定是不支持特殊行为
      support = false;
    }

  } catch (e) {
    // 如果过程中出错,保守地认为不支持
    console.error("Error detecting hidden='until-found' support:", e);
    support = false;
  } finally {
    // 6. 清理:无论如何都要从 DOM 中移除测试元素
    document.body.removeChild(testElement);
  }

  // 7. 返回结果
  return support;
}

// ---- 使用示例 ----

// 执行检测 (通常在页面加载早期执行一次即可)
const hasNativeHiddenUntilFound = supportsHiddenUntilFound();

// 根据结果进行条件操作
if (hasNativeHiddenUntilFound) {
  console.log('浏览器原生支持 hidden="until-found"!');
  // 可以放心地在 HTML 中使用 hidden="until-found"
  // 或者通过 JS 动态给元素设置这个属性
  const element = document.getElementById('my-collapsible-section');
  if (element) {
    element.setAttribute('hidden', 'until-found');
  }
} else {
  console.log('浏览器不支持 hidden="until-found",准备使用备用方案。');
  // 可能需要加载一个 Polyfill,或者使用不同的交互方式(比如手动控制显隐)
  // 例如,使用 <details>/<summary>,或者 JavaScript 控制的按钮 + div
}

解释和注意事项

  • 为啥要添加到 DOM? window.getComputedStyle() 只有在元素位于活动的文档中时才能返回最准确的、受所有 CSS 规则(包括用户代理样式表)影响的最终样式。不添加到 DOM 就去获取样式,可能得不到我们期望的结果,特别是与 display 相关的默认样式。
  • 性能考虑: 这个检测涉及 DOM 操作,虽然很快,但最好在页面初始化时执行一次,然后把结果缓存起来,避免重复执行。
  • 鲁棒性: try...finally 结构确保即使在检测过程中发生错误(理论上不太可能,但写上更保险),测试元素也能被从 DOM 中移除,不会留下垃圾。
  • 样式隐藏: 代码中给 testElement 设置了绝对定位和屏幕外坐标 (top: -9999px),这是为了确保它在添加到 body 的短暂瞬间不会引发页面内容的抖动或可见的闪烁。visibility: hidden 也是辅助措施。
  • 判断逻辑: 主要依赖于检查 display 是否为 none。这是一个间接但有效的判断。理论上,更完美的检查是直接看 content-visibility 属性,但 display 的变化更具有决定性(从有布局到无布局)。如果未来浏览器实现发生变化,这个判断逻辑可能需要微调。

进阶使用技巧

  1. 缓存结果: 如前所述,避免重复检测。可以将结果存为一个全局变量或模块状态。

    // featureDetects.js
    let _supportsHiddenUntilFound = null; // null 表示尚未检测
    
    export function checkSupportsHiddenUntilFound() {
      if (_supportsHiddenUntilFound === null) {
        // 把上面的 supportsHiddenUntilFound 函数实现放在这里
        function detect() {
          const testElement = document.createElement('div');
          testElement.setAttribute('hidden', 'until-found');
          testElement.style.position = 'absolute';
          testElement.style.visibility = 'hidden';
          // ... (省略完整实现细节)
          document.body.appendChild(testElement);
          let support = false;
          try {
            const styles = window.getComputedStyle(testElement);
            support = styles.display !== 'none';
          } finally {
            document.body.removeChild(testElement);
          }
          return support;
        }
        _supportsHiddenUntilFound = detect();
      }
      return _supportsHiddenUntilFound;
    }
    
    // 在其他地方使用:
    // import { checkSupportsHiddenUntilFound } from './featureDetects.js';
    // if (checkSupportsHiddenUntilFound()) { ... }
    
  2. 集成到项目: 可以将这个检测函数封装成一个通用的特性检测工具函数,或者集成到你的前端框架/库的初始化逻辑中。

  3. 条件加载 Polyfill 或组件: 检测到不支持时,可以动态加载一个 JavaScript Polyfill(如果存在的话,不过 hidden="until-found" 的行为比较复杂,纯 JS 模拟可能不完美),或者加载/渲染一个完全不同的 UI 组件来实现类似的可折叠/可查找功能(比如用 <details>/<summary>,或者自己写一套基于按钮控制的显隐逻辑)。

    // 假设有一个 polyfill 或备用组件加载函数 loadAlternativeAccordion()
    if (!checkSupportsHiddenUntilFound()) {
      loadAlternativeAccordion();
    }
    

方案二:为什么不推荐 User-Agent 嗅探

你可能会想:“我能不能直接检查浏览器类型和版本号(User-Agent 字符串)?”

强烈不建议这样做! 原因如下:

  • 不可靠: UA 字符串可以被用户或插件轻易修改。
  • 难维护: 你需要维护一个支持 hidden="until-found" 的浏览器版本列表,这个列表会随着浏览器更新而不断变化,非常麻烦。新的浏览器或不知名的浏览器你怎么处理?
  • 不精确: 同一个浏览器版本,可能因为某些内部设置或实验性标志(flags)的不同,导致对特性的支持情况也不同。
  • 违反最佳实践: Web 开发的最佳实践是“特性检测”,而不是“浏览器检测”。关注“能做什么”,而不是“是什么浏览器”。

UA 嗅探就像问对方“你是谁?”,然后根据身份猜测他会不会说某种语言。特性检测则是直接用那种语言问他“你会说这个吗?”,看他的反应。后者显然更直接、更可靠。

总结一下

检测浏览器是否支持 hidden="until-found" 这种特定 HTML 属性值,不能用常规的属性存在性检查。最靠谱的方法是进行 特性检测 (Feature Detection) ,通过创建一个测试元素,设置该属性值,然后检查其 计算样式 (Computed Style) ,特别是 display 属性是否为 none

编写一个简单的 JavaScript 函数来执行这个检查,并在页面加载时运行一次,缓存结果。根据检测结果,你可以决定是直接使用 hidden="until-found",还是启用备用的 UI 方案或加载 Polyfill。


相关资源参考: