你可能不知道的 23 条 CSS 选择器冷知识

525 阅读6分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

CSS 选择器 说起来大家都知道,而且每天都在使用。但是有些小细节你可能会忽略,下面我补充 23 条大家可能忽略掉的小细节。

CSS 选择器 的相关分类以及具体作用,还有如何计算 优先级 已经有了很多相关文章分析,这里我就不再赘述了。如果懒得查找可以直接看最后的参考资料。
欢迎大家在评论区补充、讨论...

基础规则

使用场景:啥场景不场景的,天天都得用,到处都得用~

  1. 一个没什么用的知识:后代组合器(一个或多个 CSS 空格字符-空格字符和/或四个控制字符之一:回车,换页,换行和制表符,且不包括其他组合器)是可以包括任意数量的 CSS 注释;
    • div.outer /**CSS 注释*/ p 是有效的。
  2. 通配符选择器是性能最低的一个CSS选择器
  3. [class~=类名].类名 功能上等价,当然权重是不一样的;
  4. 暂时没有能够选择 父元素、父元素的同级元素,或 父元素的同级元素的子元素 的选择器或者组合器。
  5. 也就是说,目前的 CSS 选择器:无法逆向选择,只能正向选择;
    • 无法通过一个指定的子类来选择一个父节点。
    • 无法通过一个特定的兄弟节点来选择这个节点之前的兄弟节点。可以通过一个特定的兄弟节点选择这个节点之后的节点。
    • juejin.cn/pin/7020712… 这条沸点就是希望逆向查找,个人认为使用纯 CSS 是做不到的。
    • 草案中的 :has 可能可以实现逆向选择
  6. :not() 伪类不能被嵌套, :not(:not(...)) 是无效的;
  7. 伪元素不能被当作 :not() 中的参数, :not(p::before) 这样的选择器也是无效的;
  8. 类选择器和伪类选择器写在一起的时候,是同时生效的而不是先后生效的。
    • 也就是说 .test:nth-child(1) 是查找当前元素的第一个兄弟元素并且有类名为 test 的元素,而不是查找类名为 test 的第一个兄弟元素。
    • :nth-child() \ :nth-last-child() \ :nth-of-type() \ :nth-last-of-type() 等等,同理
  9. 选择器匹配时是从右往左匹配,而不是从左往右。也就是说:.test1 .test2 是先找出所有的 .test2 的元素,然后再找出这些元素中祖先元素为 .test1 的元素。
  10. 伪类指定要选择的元素的特殊状态,可以在一个选择器中同时写多个伪类;
  11. 按照规范,伪元素应该使用双冒号(::)而不是单个冒号(:),以便区分伪类和伪元素。但是,由于旧版本的 W3C 规范并未对此进行特别区分,因此目前绝大多数的浏览器都同时支持使用这两种方式来表示伪元素;
  12. 一个选择器中只能使用一个伪元素。伪元素必须紧跟在语句中的简单选择器/基础选择器之后;
  13. 伪类与伪元素比较:伪类能够根据状态改变元素样式,伪元素可被用于为一个元素的特定部分应用样式;
  14. 当你使用选择器列表时,如果任何一个选择器无效 (存在语法错误),那么整条规则都会被忽略。
    • h1, ..special 由于 ..special 无效,导致 h1 的样式也不生效。
    • 但是如果分开写,h1 ..special 两个选择器,则 h1 的样式可以正常生效。

权重(优先级)

使用场景:样式覆盖、样式错乱原因排查。也就是说一般写代码不用太在意,出问题了知道怎么回事就行。

  1. 内联样式的行为是覆盖外部样式表的样式,所以可以看作最高优先级;
  2. !important 的意思是这条规则例外,直接覆盖任何其他声明;
    • 可以理解为开了 VIP,走专属通道;
    • 这是唯一可以重置内联样式的办法;
    • 那如何覆盖 !important 规则?用魔法打败魔法,写另一条 !important 规则;
  3. 优先级是基于选择器的形式进行计算的。举例:*[id="foo"] 选择了一个 ID,但是它还是作为一个属性选择器来计算自身的优先级;
  4. 为目标元素直接添加样式,永远比继承样式的优先级高。所以当同一个元素有多个声明的时候,优先级才会有意义;
  5. 下列选择符对优先级没有影响;
    • 通配选择符(*);
    • 关系选择符(+, >, ~, ' ', ||);
    • 任意选择伪类(:is())(实验中特性) 和 否定伪类(:not())对优先级没有影响。(但是,在 :is() 和 :not() 内部声明的选择器会影响优先级)。举例:div:not(.outer) pdiv.outer p 的优先级是一样的,得看定义位置才能知道谁生效;
    • 特异性调整伪类(:where())(实验中特性)对优先级没有影响,包含其内部声明的选择器。举例:div:where(.outer) pdiv p 的优先级也是一样的;
  6. :is() 内部声明的选择器会拥有相同的优先级,以最高优先级为准。具体例子可以看下面的代码示例;
  7. 选择器优先级和实际 Dom 中的距离是没有关系的。举例:body h1html h1 ,如果看 Dom 树中的距离,肯定是 bodyhtml 近,但是实际优先级比的是 CSS 定义的位置,靠后的生效;
  8. 我们一般考虑的样式声明其实只是“作者样式表中的声明(web开发人员设置的样式)“,其实还有”用户代理样式表中的声明(浏览器默认样式)“以及“用户样式表中的声明(用户自定义样式)”。另外两种样式也是会起作用的,偶尔可能需要考虑;
  9. 在早期浏览器版本,会出现“乱拳打死老师傅”的情况,举例来说就是 256 个 类 的优先级高过了 1 个 id 选择器。看到 256 这个比较特殊的数字,大家应该能够猜到原因是溢出;
<!-- :is() 内部声明的选择器会拥有相同的优先级,以最高优先级为准  -->
<style>
  div.outer .inner p {
    color: orange;
}

/* 
虽然 div:not(.xxx .xxx) 没有命中任何元素,
但是由于 :is 的存在,
提升了 div p 的优先级
导致 div p 的优先级和 div:not(.xxx .xxx) p 的优先级相同
也就是和 div.outer .inner p 优先级相同
*/
:is(div, div:not(.xxx .xxx)) p {
  color: blueviolet;
}
</style>

  <!-- /* 最终所有的文本颜色都是 blueviolet */ -->
<div class="outer">
  <p>This is in the outer div.</p>
  <div class="inner">
    <p>This text is in the inner div.</p>
  </div>
</div>

specifishity.png

参考资料

CSS 选择器
优先级 性能最低的一个CSS选择器
:has
前端布局必须了解的css选择器
攻克CSS记录(1)— 选择器
有趣:256个class选择器可以干掉1个id选择器