CSS 选择器

Posted: 11.16.2019

#CSS小技巧 #面试问题 - CSS

介绍

CSS 选择器可以说是 CSS 最重要的一环了。

用对选择器,可以很轻易地实现事半功倍的效果。

不过有些选择器平时用得比较少,对于它们的优先级也比较迷。

在这里先讲一下一些平时用得比较少的选择器。

属性选择器

讲道理,属性选择器用得还是很多的。不过刚开始学 CSS 的人大概率不会。

语法如下:选择器[属性=?]

好吧,这么看确实很迷,还是直接例子走起吧。

<div id="the-test"></div>

如果有这么一个 HTML 元素,我们要怎么用属性选择器选择呢?

div[id="the-test"] {
  width: 100px;
  height: 100px;
  background: pink;
}

成功了。

div[id="the-test"] 就是在说,对于所有 div 标签,选择所有 id 为 the-test 的标签。

如果把 div 去掉,[id="the-test"],我们就是在选择所有 id 为 the-test 的标签,同样也能选择到这个标签。

如果把 the-test 去掉,div[id],我们就是在选择所有具有 id 这个属性的标签,自然也能选择到这个标签。

Vue 的 scoped

Vue style 的 scoped 属性我想大家都应该用过。这玩意儿就是靠属性选择器实现的。

vue selector

Vue 会给 scoped 的标签添加一个额外的属性,然后通过属性选择器来选择这个属性,如下:

vue selector2

子元素选择器

这玩意儿其实选择的就是直接的下一代,简单来说就是下一层子元素里的标签。

<ul>
  <li class="test-sb">a</li>
  <li>b</li>
  <li class="test-sb">c</li>
  <div>
    <!-- 隔代了,所以无效 -->
    <li class="test-sb">d</li>
  </div>
</ul>
ul > .test-sb {
  color: crimson;
}

猜猜看结果是怎样的?

  • a
  • b
  • c
  • d

相邻兄弟选择器

可选择紧接在另一元素后的元素,且二者有相同父元素。

<div id="adj-test">
  <ul>
    <!-- 作为第一个 li 被选中,让下一个 li 被染色,自己没有被染色 -->
    <li>List item 1</li>
    <!-- 作为第一个 li 被选中,让下一个 li 被染色 -->
    <li>List item 2</li>
    <!-- 作为第一个 li 被选中,让下一个 li 被染色,但是没有下一个 li 了 -->
    <li>List item 3</li>
  </ul>
  <ol>
    <!-- 作为第一个 li 被选中,让下一个 li 被染色,但接下来不是 li -->
    <li>List item 1</li>
    <!-- 不是接着 li 的 li 标签,因此无法染色 -->
    <span>List item 2</span>
    <!-- 不是接着 li 的 li 标签,因此无法染色 -->
    <li>List item 3</li>
  </ol>
</div>
li + li {
  color: crimson;
}

猜猜看结果?

  • List item 1
  • List item 2
  • List item 3
  1. List item 1
  2. List item 2
  3. List item 3

普通兄弟选择器

这个选择器算是出现得比较少的。和相邻兄弟选择器比起来,它会选择后面所有的兄弟,而不是紧接的兄弟。

<div id="adj-test">
  <ul>
    <!-- 作为第一个 li 被选中,让接下来所有的 li 被染色,自己没有被染色 -->
    <li>List item 1</li>
    <!-- 作为第一个 li 被选中,让接下来所有的 li 被染色 -->
    <li>List item 2</li>
    <!-- 作为第一个 li 被选中,让接下来所有的 li 被染色 -->
    <li>List item 3</li>
  </ul>
  <ol>
    <!-- 作为第一个 li 被选中,让接下来所有的 li 被染色,自己没有被染色 -->
    <li>List item 1</li>
    <!-- 不是接着 li 的 li 标签,因此无法染色 -->
    <span>List item 2</span>
    <!-- 因为是第一个 li 的兄弟,因此被染色 -->
    <li>List item 3</li>
  </ol>
</div>
li ~ li {
  color: crimson;
}
  • List item 1
  • List item 2
  • List item 3
  1. List item 1
  2. List item 2
  3. List item 3

伪类选择器

这个大家肯定用过,不过又有谁知道一共有多少种伪类呢?

pseudoclass selector

具体的链接在这里,感兴趣的朋友可以自己去看:Pseudo-classes

伪元素选择器

伪元素和伪类不一样,伪元素选中的都是某个标签的一部分。

比如 ::before 和 ::after 这俩用得比较多的,::after 就在 清除浮动 里会用到。

不过有一点需要提一下,那就是写伪元素选择器的时候,一个冒号或者两个冒号都行。所有的现代浏览器的最新版本,两种都支持,但有的浏览器老的版本是只支持一个冒号的。因此,虽然两个冒号才是正宗的写法,但一般来说,出于兼容性的考虑,都是写一个冒号的。这样比较安全。

具体的兼容性和伪元素种类,可以参考这里:Pseudo-elements

pseudo-element

优先级

接下来就是最重的重头戏了:CSS 选择器的优先级。

首先看看我们一共有哪些选择器:

  • 标签选择器
  • 类选择器
  • ID 选择器
  • 属性选择器
  • 后代选择器
  • 子元素选择器
  • 相邻兄弟选择器
  • 普通兄弟选择器
  • 伪类选择器
  • 伪元素选择器
  • 全局选择器

OK,开始排序吧。CSS 选择器的优先级,从最优先的到最不优先的:

  1. ID 选择器
  2. 类选择器 = 伪类选择器 = 属性选择器
  3. 标签选择器 = 伪元素选择器
  4. 全局选择器

但是这么看根本不够,因为很多时候我们都是多个选择器一起用的。

因此,有一种计算方法是:

  • 给 ID 选择器权重 = 1000
  • 给类选择器这一层的权重 = 100
  • 给标签选择器这一层的权重 = 10
  • 给全局选择器权重 = 1
  • 相邻兄弟选择器、普通兄弟选择器、后代选择器、子元素选择器,本身没有权重

权重的比较规则如下:

  1. 计算多个标签或者选择器的时候,就把这些权重加起来,权重高的优先度就高
  2. 如果权重一样,那么后定义的属性会覆盖先定义的属性

比如 #a .b + c = 1000 + 100 + 10 = 1110

#a #b > .c = 1000 + 1000 + 100 = 2100

因此,下面的选择方法优先度更高。

但有一点需要注意的是,这只是一种模拟,并不是真的如此。

给一个元素定义 101 个类,然后全部选择,并不能比单独的 ID 选择器优先度更高。

优先度更高的选择器,优先度永远比优先度低的选择器高。

额外需要提一点

伪类选择器和属性选择器都不是一个单独的标签,因此需要把它们之前的标签也一起计算进去

div:nth-of-type(1)div[id] 其实是同一优先级的。

而和 .abc 类选择器比起来,它们其实是 div + 自己,比类选择器多一个 div,因此它们的优先度更高。

计算它们的时候,需要把它们之前的标签也一起计算进去,不然就会出错。

额外再需要提一点

在普通的选择器之外,还有两个法外狂徒:inline 和 important。

加上它们两个,优先级其实是:important > inline > 所有的选择器。

参考资料

CSS selectors

CSS 相邻兄弟选择器

Pseudo-classes

Pseudo-elements