CSS里那些奇奇怪怪的选择器,到底能不能用?来盘一盘

1,680字
7–11 分钟
in

最近在网上冲浪,看到有个前端老哥拿CSS选择器整了个花活儿,把一些平时不太起眼甚至有点反直觉的选择器用法给扒了出来。说实话,有些写法确实让人眼前一亮,但更多像是写着玩儿的“骚操作”。这年头写样式,谁还没遇到过几个看着眼熟但真用起来又拿不准的选择器呢?与其在项目里瞎试,不如一次性把这些“老朋友”的底裤扒干净。

目录

为什么:root比html更好用?

咱们写全局CSS变量的时候,总能看到:root的身影。这玩意儿其实是个伪类,在HTML文档里它指的就是<html>根元素,但它的特异性比元素选择器高那么一丢丢(伪类是0-1-0,元素是0-0-1),所以用它来定义全局样式不容易被后续的样式不小心覆盖掉。

场景:root匹配html匹配
HTML文档<html><html>
SVG文档<svg>无匹配
RSS文档<rss>无匹配
MathML文档<math>无匹配

要是碰上SVG这种XML文档,:root照样能精准匹配到<svg>元素,但html选择器就直接歇菜了。所以跨文档场景下,:root明显更抗揍。那:scope又是啥情况?它俩在全局作用域下效果基本没差,:scope语义上更贴近“全局作用域”这个概念,但实际用起来,:root已经是江湖老规矩了,新手跟着抄作业就行。

不嵌套的&也能选html?

CSS嵌套里的&符号,平时都是跟父选择器绑在一起用的,比如.card { &:hover { ... } },这么写&会变成.card:hover。但要是把&单独拎出来,放在最外层,它会直接认祖归宗,变成根元素的引用。

新手容易踩的坑就在这里:如果在@scope规则里用&,它指的是当前作用域的根,而不是全局的<html>。有些老项目为了省事,直接拿& { ... }来定义全局重置样式,看起来挺唬人,实际上跟直接写:root效果一样,但这种写法容易让后面接手的人看得一脸懵逼。

:has(head) 这种操作图啥?

:has(head) 或者 :has(body) 这种选择器,乍一看好像没啥用,但它背后有个硬性规则:<html> 的直接子元素只能是 <head><body>。这俩标签在其他任何地方出现都是非法的,浏览器虽然会好心肠地帮你纠正,但万一有人手滑在 <body> 里又塞了个 <body>,页面渲染就会出幺蛾子。

好比炒菜时把锅铲放冰箱里,浏览器这个“厨师”虽然能给你捞出来,但火候早就乱了。所以 :has(head) 这种写法就像是给HTML文档做了个“户口普查”,只要页面结构不犯二,能匹配上的只有 <html> 这个老祖宗。不过这玩意儿纯属炫技,实际项目里用到的概率,比中彩票还低。

:not(* *) 抓的是哪路神仙?

:not(* *) 这种写法,把通用选择器和后代选择器揉一块了。* * 的意思是“任何元素的子元素”,外面再套个 :not(),就是要找出那些不是任何元素子元素的元素。

整个文档里符合这个条件的,只有 <html> 这个顶梁柱,因为它没有父元素。要是写成 :not(* > *),那就更好玩了,这就像是在说“抓出那些没有直接父元素的孩子”,结果还是只有 <html> 孤零零地站着。

选择器匹配结果实用指数
:not(* *)没有父元素的元素一颗星
:not(* > *)没有直接父元素的元素也是一颗星

这种选择器就像用牛刀杀鸡,理论上能跑,但实战中谁要是这么写,代码审查那关肯定过不去。不过知道有这么回事儿,以后看别人秀操作时至少能接住梗。

实际操作的时候,新手很容易被这些冷门选择器绕晕。比如在组件库里想给根节点加样式,直接用 :root 准没错,但要是想针对某个SVG图标单独调色,html 选择器就失灵了,得用 :root 或者直接选 svg 元素。再比如写CSS变量时,:root:scope 在全局下确实没区别,但 :scope@scope 规则里就跟开盲盒一样,作用域一换,指向就变了。

写样式这事儿,跟做菜差不多,调料(选择器)越多,越得知道什么时候该放、放多少。有些调料(比如 :has(head))可能一辈子也用不上,但知道它放在哪个格子,至少不会在需要用 :root 的时候,顺手抄起一把 :not(* *) 往锅里撒,那就真成黑暗料理了。