Styles 面板的 specificity 提示只给 (1,3,3) 三个数——Chrome DevTools 现在把 #nav 投 a、:hover 投 b、ul 投 c 逐行列出来
Chrome DevTools 的 Styles 面板里,hover 一个选择器只会弹出 (1, 3, 3) 这样一个三元组——现在这个提示升级成了逐段拆解:a 层是 #nav、b 层是 .menu / :hover / [href^=”https”]、c 层是 ul / li / a,每一票写得明明白白,CSS 优先级调试终于不用在脑子里数票了。
先从一个真实的调试场景说起
导航栏做过主题改版后,这条规则时灵时不灵:
/* 场景:导航链接的悬停色,被后来的规则压掉了 */
#nav ul.menu > li:hover a[href^="https"] {
color: #c00;
}
你选中元素,看到这条规则被划掉,hover 选择器,tooltip 弹出 (1, 3, 3)。问题来了:这 3 个 b 到底是谁投的?:hover 算 b 还是 c?[href^=”https”] 又算哪层?按 CSS Selectors Level 4 的算法,伪类和属性选择器都进 b 层——但没把规则数过的人,十次有八次要犹豫一下,然后再打开 MDN 确认一遍。
Before / After:从三个数到逐行拆解
Before(一直以来的样子):
hover 选择器 → (1, 3, 3) ← 只有结果,没有过程
After(Bug 353436867 修完之后):
a (1): #nav
b (3): .menu, :hover, [href^="https"]
c (3): ul, li, a
哪一层输了、是哪个简单选择器把分拉高的,一眼定位,不用心算。
为什么这个功能拖到现在才做
最省事的方案是在 DevTools 前端用 TypeScript 重新解析选择器、自己分类——Chromium 团队直接拒绝了,理由很硬:这等于维护第二套 CSS 选择器解析器,必然会和真实引擎漂移。而难住前端的恰好是 tooltip 最有用的那几种场景:
:is()和:has()取其参数里 specificity 最高的一个:where()无条件贡献 0,里面写什么都不算:not()取参数里最高的,但它自己不按伪类计票:nth-child(2n of .foo)把伪类计数和选择器列表参数混算
Blink 的 CSSSelector::Specificity() 早把这些情况都算对了。于是两笔 CL 走了「引擎算、协议传、前端渲染」的路子:CDP 侧 c/7793254 于 2026-06-16 合入(+114/-16),把每个简单选择器的贡献挂到已有的 CSS.Specificity 结构上;DevTools 前端 c/7645008 于 6-25 合入,只负责渲染引擎报上来的答案——没有任何客户端解析,零漂移。
还有一层红利:因为拆解走的是 Chrome DevTools Protocol,编辑器、linter、跑在 DevTools MCP 上的 AI agent 都能拿到引擎级准确的拆解数据,而不是工具自己算的近似值。
拿到功能后的三步调试流程
- 选中样式失效的元素,在 Styles 面板找到被划掉的规则;
- hover 两个冲突的选择器,逐层对比拆解,哪层高当场见分晓;
- 修复时优先调选择器而不是加
!important:跨组件压不住就用@layer收编层级,基线样式用:where()把权重归零——比如.card :where(h2)是 (0, 1, 0),而:where(.card h2)直接是 (0, 0, 0),后续规则想覆盖它不需要任何权术。
下一步:今天打开 DevTools,把你项目里最长的那条选择器 hover 一遍,对照拆解数一遍——如果 a 层里出现了本不该有的 #id,那就是下一个该重构的选择器。
评论区
登录后可评论。