You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Chrome 65+ CSS嵌套计数器动态子元素泄漏问题求助

CSS计数器在Chrome 65+动态显示子菜单时的泄漏问题修复

我碰到过类似的Chrome 65之后出现的CSS计数器Bug,先还原下你的问题场景——你用CSS计数器实现嵌套菜单编号,但动态显示/隐藏子菜单时,子菜单的计数器会“泄漏”到父元素的同级项里,导致编号混乱。

先看你的原始代码:

<style> 
ul { counter-reset: test; } 
ul li::before { 
  counter-increment: test; 
  content: counters(test, ".") " "; 
} 
</style> 

<button onclick="document.getElementById('submenu').style.display='none';">HIDE</button> 
<button onclick="document.getElementById('submenu').style.display='block';">SHOW</button>

<ul>
  <li>父项1</li>
  <li>父项2
    <ul id="submenu">
      <li>子项2-1</li>
      <li>子项2-2</li>
    </ul>
  </li>
  <li>父项3</li>
</ul>

问题原因

这个Bug是Chrome在65版本后对CSS计数器上下文的处理逻辑变化导致的:当动态显示包含计数器的子元素时,浏览器没有正确重置父容器的计数器上下文,子菜单的计数器增量被错误地继承到了父级的同级元素中,比如原本父项3应该是3,结果变成了4(因为子菜单的两个项让计数器多增了2次)。

修复方案

核心思路是让子菜单的计数器和父级计数器完全隔离,避免上下文混乱。这里提供两种兼容不同场景的方案:

方案1:使用:has()选择器(现代浏览器推荐)

利用:has()检测包含子菜单的父项,给它单独重置子计数器:

<style>
  ul { counter-reset: test; }
  /* 给包含子菜单的父项单独重置子计数器 */
  ul li:has(> ul) { counter-reset: sub-test; }
  /* 父级项的计数器逻辑 */
  ul li::before { 
    counter-increment: test; 
    content: counters(test, ".") " "; 
  }
  /* 子菜单使用独立的计数器 */
  ul li > ul { counter-reset: sub-test; }
  ul li > ul li::before {
    counter-increment: sub-test;
    content: counters(test, ".") "." counters(sub-test, ".") " ";
  }
</style>

<button onclick="document.getElementById('submenu').style.display='none';">HIDE</button>
<button onclick="document.getElementById('submenu').style.display='block';">SHOW</button>

<ul>
  <li>父项1</li>
  <li>父项2
    <ul id="submenu">
      <li>子项2-1</li>
      <li>子项2-2</li>
    </ul>
  </li>
  <li>父项3</li>
</ul>

方案2:给父项手动加类(兼容旧浏览器)

如果你的项目需要兼容不支持:has()的浏览器,可以给包含子菜单的父项添加类名,通过类来隔离计数器:

<style>
  ul { counter-reset: test; }
  /* 标记有子菜单的父项 */
  .has-submenu { counter-reset: sub-test; }
  ul li::before { 
    counter-increment: test; 
    content: counters(test, ".") " "; 
  }
  ul li > ul { counter-reset: sub-test; }
  ul li > ul li::before {
    counter-increment: sub-test;
    content: counters(test, ".") "." counters(sub-test, ".") " ";
  }
</style>

<button onclick="document.getElementById('submenu').style.display='none';">HIDE</button>
<button onclick="document.getElementById('submenu').style.display='block';">SHOW</button>

<ul>
  <li>父项1</li>
  <li class="has-submenu">父项2
    <ul id="submenu">
      <li>子项2-1</li>
      <li>子项2-2</li>
    </ul>
  </li>
  <li>父项3</li>
</ul>

备选方案:使用contain属性强制隔离渲染上下文

如果只需要兼容现代浏览器,也可以用contain: layout强制浏览器隔离父项的渲染上下文,避免计数器泄漏:

ul li:has(> ul) {
  contain: layout;
}

这样修改后,不管你怎么切换子菜单的显示/隐藏,父级的同级项计数器都不会被干扰了。

内容的提问来源于stack exchange,提问作者Justinas Staskus

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 04:29:33