CSS 选择器
CSS 的选择器划分有如下几种
- 简单选择器
- 复合选择器
- 复杂选择器
其中复合选择器和复杂选择器都是由简单选择器组合而成,这可以借助一些连接符完成。
简单选择器
CSS 选择器最底层的结构,组成了简单选择器,简单选择器可以分为以下几种类型:
-
通配符*,*选择器可以命中所有的 html 元素
-
标签选择器(Type),命中如 div、span、p 等 html 元素标签
-
id 选择器,以#号开头,如 #abc 命中 html 中元素属性名为“id”,属性值为“abc”的元素
-
类选择器(也叫 class 选择器),如 .abc 命中 html 中元素属性名为“class”,属性值为“abc”的元素
-
属性选择器,使用“[]” 中括号包裹的内容,属性选择器一般与其他选择器一起使用作为补充,如 div[id],命中的是所有带有 id 属性的 div 元素,div[id=“abc”]命中 id 属性值为 abc 的 div 元素
-
伪类选择器,与其他的选择器组合使用,用来表示该元素的某种状态,如 div:hover,表示当 div 元素被鼠标悬浮时的样式,常见的伪类还有 :active、:link、:focus 等等
-
伪元素选择器,伪元素顾名思义即为不真实的元素,需要配合其他选择器一起使用,单独使用没有意义,如 div[class="abc"]::after,用来在命中属性 class 的 div 元素之后生成一个新的元素,主要的伪元素选择器有 ::before、::after、::first-letter、::first-line
简单选择器的表现形式如下:
*
div svg|a
#id
.cls
[attr=value]
:hover
::before
复合选择器
通过简单选择器的不间断连写,可以组成复合选择器,它的产生式可以表述为:
<复合选择器> = <简单选择器><简单选择器><简单选择器><简单选择器><简单选择器>
如果组成复合选择器的简单选择器中有标签选择器,则必须要让标签选择器置于最前方的位置,伪类和伪元素需要放在最后面。复合选择器的表现形式如下:
div.abc[class="123"]
复杂选择器
复杂选择器是利用复合选择器和一些连接符来组成,它的产生式可以表述为:
<复杂选择器> = <复合选择器> [ "space" | ">" | "~" | "+" | "||"] <复合选择器>
其中 space 的意思是空格,即两个复合选择器之间可以通过空格进行分割。
举例说明
下面通过几个案例说明几种连接符的含义。
div .cls
本例演示空格的作用,该选择器的含义是选择 div 元素下的所有带有 cls 的 class 属性的子孙元素
<div>
<div class="cls">
<a class="cls"></a>
</div>
</div>
如上可以选择命中 div 和 a 标签
div > .cls
本例演示">"的作用,该选择器的含义是选择 div 元素下的所有带有 cls 的 class 属性的子元素,不包含孙元素
<div>
<div class="cls">
<a class="cls"></a>
</div>
</div>
如上可以选择命中 div 而不能选中 a
div ~ .cls
本例演示"~"的作用,该选择器的含义是选择与 div 元素同级的并且在 div 元素之后 class 属性值为 cls 的元素
<div>
<div class="cls">
<a class="cls" id="1"></a>
</div>
<a class="cls" id="2"></a>
<span class="cls" id="3"></span>
</div>
如上可以选择命中 id 为 2 的 a 和 id 为 3 的 span 而不能选中 id 为 1 的 a
div + .cls
本例演示"+"的作用,该选择器的含义是选择与 div 元素同级的并与 div 相邻 class 属性值为 cls 的元素
<div>
<div class="cls">
<a class="cls" id="1"></a>
</div>
<a class="cls" id="2"></a>
<span class="cls" id="3"></span>
</div>
如上可以选择命中 id 为 2 的 a 而不能选中 id 为 3 的 span 和 id 为 1 的 a,不能选中 span 是因为不相邻,中间有间隔一个元素
|| (不重要)
为 Selector4 的语法,用来选中表格中的列,目前浏览器的实现支持较少,不推荐使用仅作为知识的补充
选择器优先级
在拥有了多种类型组合的前提下,我们该如何使用这些选择器呢?假如若干个选择器组合共同作用到同一个元素上,浏览器该使用哪一条属性来最终产生效果呢?这里就需要知道 CSS 选择器的优先级了。
优先级的概念
首先对优先级做一个宏观的概念普及:
- 优先级就是分配给指定的 CSS 声明的一个权重,它由匹配的选择器中的每一种选择器类型的数值决定。
- 当优先级与多个 CSS 声明中任意一个声明的优先级相等的时候,CSS 中最后的那个声明将会被应用到元素上。
- 当同一个元素有多个声明的时候,优先级才会有意义。因为每一个直接作用于元素的 CSS 规则总是会接管/覆盖(take over)该元素从祖先元素继承而来的规则。
关于优先级: 浏览器通过优先级来判断哪些属性值与一个元素最为相关,从而在该元素上应用这些属性值。优先级是基于不同种类选择器组成的匹配规则。最新标准中选择器的权重由三元组进行计算得到,分别是:[id,class,标签]。
优先级的产生
下面列表中,选择器类型的优先级是递增的:
- 类型选择器(例如 h1)和伪元素(例如::before),类型选择器也叫标签选择器
- 类选择器 (例如.example)、属性选择器(例如[type="radio"])和伪类(例如:hover),注意[id="abc"]也是属性选择器,不是 ID 选择器
- ID 选择器(例如#example)。
通配选择符(universal selector)(*)、关系选择符(combinators)(+, >, ~, ' ', ||)和 否定伪类(negation pseudo-class):not()对优先级没有影响。但是在 :not() 内部声明的选择器会影响优先级。
给元素添加的内联样式(例如 style="font-weight:bold")总会覆盖外部样式表的任何样式,因此可看作是具有最高的优先级。
所以当我们使用一个三元组来收集选择器的时候,可以得到: [ id 选择器,类选择器,标签选择器]。 按照官方标准中的计算方式,假定一个数字为 N,三元组中的每个位置的数量为进制位数,则:
优先级 = id * N^2+ 类 * N^1 + 标签 * N^0
若给定数字为 1000,则可以通过计算式得到优先级。
例如选择器: div#id p.cls -->对应的三元组是[1,1,2]-->即 1 个 id,1 个类,2 个标签 --> 优先级 = 1 * 1000^2 + 1 * 1000^1 + 2* 1000^0。
优先级的破坏
当在一个样式声明中使用一个 !important 规则时,此声明将覆盖任何其他声明。
虽然,从技术上讲,!important 与优先级无关,但它与最终的结果直接相关。使用 !important 是一个坏习惯,应该尽量避免,因为这破坏了样式表中的固有的级联规则 使得调试找 bug 变得更加困难了。当两条相互冲突的带有 !important 规则的声明被应用到相同的元素上时,拥有更大优先级的声明将会被采用。
一些经验法则:
- 一定要优先考虑使用样式规则的优先级来解决问题而不是 !important
- 只有在需要覆盖全站或外部 CSS 的特定页面中使用 !important
- 永远不要在你的插件中使用 !important
- 永远不要在全站范围的 CSS 代码中使用 !important
避免使用 !important
- 更好地利用 CSS 级联属性
- 使用更具体的规则。在您选择的元素之前,增加一个或多个其他元素,使选择器变得更加具体,并获得更高的优先级。
<div id="test">
<span>Text</span>
</div>
<style>
div#test span {
color: green;
}
div span {
color: blue;
}
span {
color: red;
}
</style>
无论 css 语句的顺序是什么样的,文本都会是绿色的(green),因为这一条规则是最有针对性、优先级最高的。(同理,无论语句顺序怎样,蓝色 blue 的规则都会覆盖红色 red 的规则)
怎么使用 !important
覆盖内联样式
你的网站上有一个设定了全站样式的 CSS 文件,同时你(或是你同事)写了一些很差的内联样式。
全局的 CSS 文件会在全局范围内设置网站的外观,而直接在各个元素上定义的内联样式可能会覆盖您的全局 CSS 文件。 内联样式和 !important 都被认为是非常不好的做法,但是有时你可以在 CSS 文件里用!important 去覆盖内联样式。
在这种情况下,你就可以在你全局的 CSS 文件中写一些 !important 的样式来覆盖掉那些直接写在元素上的行内样式。
<div class="foo" style="color: red;">What color am I?</div>
<style>
.foo[style*="color: red"] {
color: firebrick !important;
}
</style>
许多 JavaScript 框架和库都添加了内联样式。 有时候可以用!important 与优先级高的选择器一起使用,以重写覆盖这些内联样式。
覆盖优先级高的选择器
在外层有 #someElement 的情况下,你怎样能使 awesome 的段落变成红色呢?这种情况下,如果不使用 !important ,第一条规则永远比第二条的优先级更高。
<style>
#someElement p {
color: blue;
}
p.awesome {
color: red;
}
</style>
怎样覆盖 !important
- 很简单,只需再添加一条 带 !important 的 CSS 规则,再给这个选择器更高的优先级(添加一个标签,ID 或类);或是添加一样选择器,把它的位置放在原有声明的后面(总之,最后定义一条规则比胜)。一些拥有更高优先级的例子如下:
<style>
table td {
height: 50px !important;
}
.myTable td {
height: 50px !important;
}
#myTable td {
height: 50px !important;
}
</style>
- 使用相同的选择器,但是置于已有的样式之后:
<style>
td {
height: 50px !important;
}
</style>
- 改写原来的规则,避免使用 !important。
<style>
[id="someElement"] p {
color: blue;
}
p.awesome {
color: red;
}
</style>
将 id 作为属性选择器的一部分而不是 id 选择器,将使其具有与类相同的特异性。 上面的两个选择器现在具有相同的权重。在优先级相同情况下,后面定义的 CSS 样式会被应用。
优先级的几个场景
基于形式的优先级
优先级是基于选择器的形式进行计算的(Form-based specificity)。在下面的例子中,尽管选择器*[id="foo"] 选择了一个 ID,但是它还是作为一个属性选择器来计算自身的优先级。
有如下样式声明:
<style>
*#foo {
color: green;
}
*[id="foo"] {
color: purple;
}
</style>
虽然匹配了相同的元素,但是 ID 选择器拥有更高的优先级。所以第一条样式声明生效。
无视 DOM 树中的距离
有如下样式声明:
<style>
body h1 {
color: green;
}
html h1 {
color: purple;
}
</style>
当它应用在下面的 HTML 时:
<html>
<body>
<h1>Here is a title!</h1>
</body>
</html>
由于 html h1 写在之后,相同优先级的前提下,浏览器会将它渲染成 purple
直接给目标元素添加样式
为目标元素直接添加样式,永远比继承父节点样式的优先级高,无视优先级的遗传规则。
<style>
#parent {
color: green;
}
h1 {
color: purple;
}
</style>
当它应用在下面的 HTML 时:
<html>
<body id="parent">
<h1>Here is a title!</h1>
</body>
</html>
浏览器会将它渲染成 purple,因为 h1 选择器明确的定位到了元素,#parent 选中的是 body ,绿色仅仅继承自其父级。
总结
- 从父元素继承的样式具有最低优先级,任何选择器都可以改变其最终作用的样式,如*
- 不管在 DOM 树中,是不是看起来节点间离的更近,只要优先级相同,写在样式表文件中之后的属性生效
- 优先级的计算是基于形式的,属性选择器中利用 ID 来选择仍然是属性选择器,比 ID 选择器本身的优先级低
- 优先级的计算跟选择器的写法和顺序无关,只要最终产生的各类选择器的个数相同并且选中同一个元素,那么优先级就是相同的,之后按照总结 2 的规则
- 行内元素样式具有最高的优先级,写在行内的元素样式总是会覆盖外部的样式
- !important 可以破坏整个优先级的计算获得最高优先级,除非是在对一些内联样式进行覆盖时,在其他使用场景下都要尽量使用优先级处理覆盖,!important 和内联样式都是不好的习惯
- 最佳的样式表使用经验是:对元素进行基本的样式设定,当遇到需要定制化样式的时候,增加 class 类型选择器来覆盖样式,id 选择器尽量不要使用
另外需要注意的是
- 样式的作用总是从选择器中最后一个获取,最后一个选择器一定是最先被匹配,如 div.cls #id p, p 最先被取用然后会匹配到文档中的所有的 p,再去找它的父节点。因此在考虑 css 优化时,最好不要将标签属性写在最后
- 会触发重绘的样式属性不推荐使用!如 :last-child、:nth-last-child 这类
参考资料:
- MDN-优先级
- 选择器优先级-老的标准
- 选择器优先级-新标准
- Specifishity 图文网站