Skip to content

CSS常见选择器及权重特异性详解

嘛,说到写博客时的CSS样式,
果然还是会遇到一些规则“打架”的麻烦事吧。
明明写了正确的样式,
可页面元素还是我行我素,
最后只好掏出!important,
让代码的可读性变得一团糟了呢/(ㄒoㄒ)/~~
实则不然,
这一切的背后,
可是有**特异性(Specificity)**的这套规则在悄悄运作哦。
毕竟只有明白了这套规则,
才能写出优雅可控的CSS呢。
(笑

当然,我们先需要好好认识一下CSS选择器的种类

先说说最基础的吧

1.CSS选择器种类

元素选择器

元素选择器是最常见的CSS选择器之一,它通过选择在一整个HTML文件中相匹配的标签,来赋予相匹配的样式。

语法形如

1
2
3
4
5
元素名称{
样式属性:属性值;
样式属性:属性值;
......
}

比如下面的示例↓

1
2
3
4
h1{
color:yellow;
font-size:64px;
}

这段示例将会把所有h1标签包裹的元素的颜色设置为黄色,文字大小设置为64px。

当然,这种方式一次给所有的同种标签来设置统一样式。可是嘛,用baka的脑子都会想到,当我们想给某些特定段落给特殊待遇的时候,这就有点相形见绌了。(´ー∀ー`)

类选择器

类选择器通过选择在整个HTML文件中,具有相同定义属性的元素,来赋予相匹配的样式。

语法形如

1
2
3
4
5
.class属性名称{
样式属性:属性值;
样式属性:属性值;
......
}

使用语法格式形如:

1
<标签名称 class="类选择器名称1 类选择器名称2.....">

在类选择器中,你可以同时在一个标签中同时叠加两个以上的类选择器,其最终样式会叠加。当然,如果你使用两个以上的类选择器对同一个样式进行定义,那么最后定义的样式会起作用。

比如下面的实例↓

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!DOCTYPE html>
<html>
<head>
<style>
.class1{
color:red;
}
.class1{
color:blue;
font-size:62px;
}
.class2{
text-decoration: underline;
}
</style>
</head>
<body>
<h1 class="class1">样式1</h1>
<h2 class="class1 class2">样式1和样式2的叠加</h2>
</body>
</html>

在这段实例中,我们对class1这个样式定义了两次,所以只有第二个会生效。而后我们又对h2标签使用了两个类选择器,最后会呈现叠加的效果,就像下面这样。ヾ(•ω•`)o

你可能会注意到,这种方式可能会比一股脑地全选好一些,当然地,不过如果有另一个指令也用类选择器来定义了冲突的样式话捏,那就要看他们谁在谁的后面。

ID选择器

ID选择器的作用效果与类选择器相似,但也有几点不同(°ー°〃):

  • ID选择器在引用时是使用ID属性

  • ID选择器的语法规定前面使用#号来引入

  • 在同一个HTML文档中,只允许使用一次同一ID选择器(也就是说,不同的标签不可以拥有相同的ID,但可以拥有相同的class属性。这表明了ID选择器具有唯一性。)

  • ID选择器不能叠加使用

语法形如

1
2
3
4
5
#ID名称{
样式属性:属性值;
样式属性:属性值;
......
}

使用语法格式形如:

1
<标签名称 id="id名称">

比如下面的示例↓

1
2
3
4
#exampleid1{
color:yellow;
font-size:64px;
}

这段示例,将会把拥有exampleid1的标签所包裹的元素的颜色设置为黄色,文字大小设置为64px。

显然地,这是最精确的方式,一次只选择唯一的相匹配的标签。只因为如此,当给某个元素下指令时候捏,其他的选择器通常竞争不过它,除非有特殊情况。

后代选择器 父子选择器

我们先看一段代码片段:

1
2
3
4
5
6
7
8
9
<div>
<p>直接子元素段落</p> <!-- 层级1 -->
<section>
<p>孙子元素段落</p> <!-- 层级2 -->
<div>
<p>曾孙元素段落</p> <!-- 层级3 -->
</div>
</section>
</div>

后代选择器和父子选择器都是选择子元素的选择器。不同的是,后代选择器可以选择嵌套多层的元素,在上面的实例中,后代选择器可以直接选择到层级三(包含孙子和曾孙元素段落),而父子选择器只能选择到层级二(仅有孙子元素段落)。

后代选择器语法形如

1
2
3
4
5
祖先标签 后代标签{
样式属性:属性值;
样式属性:属性值;
......
}

父子选择器语法形如

1
2
3
4
5
父标签>子标签{
样式属性:属性值;
样式属性:属性值;
......
}

参照下面的示例,你可以更好体会它。Tips:在这段实例中,后代选择器和父子选择器权重相同。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!DOCTYPE html>
<html>
<head>
<style>
h2 span{
color:blue;
font-size:62px;
}
h2>span{
text-decoration: underline;
}
</style>
</head>
<body>
<h2>hello
<span>world</span>
</h2>
<h2>hello
<div><span>world</span></div>
</h2>
</body>
</html>

在这段实例中,由于后代选择器可以可以选择嵌套多层的元素,所以第一个hello world的world(层级二)和第二个hello world的world(层级三)均可以被添加为为蓝色以及62px。而又因为父子选择器只能选择到层级二,所以只有第一个hello world的world(层级二)被添加underline。

这种选择器它通过描述谁里面的谁来精准定位目标。而且,它的优先性由它的几个部分叠加所成。(⊙o⊙)

其他

  • 伪类选择器:适用于选择处于特殊状态的元素,不是元素的固定属性,而是元素的临时状态。
    语法形如:
    选择器 伪类{
    样式属性:属性值;
    样式属性:属性值;
    ……
    }

  • 分组选择器:如果有些选择器定义的样式完全相同,那么就可以使用分组选择器来定义相同的样式。
    语法形如:
    标签1,标签2……{
    样式属性:属性值;
    样式属性:属性值;
    ……
    }

  • 相邻选择器:如果两个元素拥有一个相同的父元素,那么可以使用相邻选择器。
    语法形如:
    子标签1+子标签2{
    样式属性:属性值;
    样式属性:属性值;
    ……
    }

  • 属性选择器:可以选择有指定属性的元素进行样式设置,比如超链接。
    语法形如:
    标签名称[属性]{
    样式属性:属性值;
    样式属性:属性值;
    ……
    }

那么,一个很自然的问题就来了……

现在你知道了这些种类繁多的CSS选择器。

但……

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!DOCTYPE html>
<html>
<head>
<style>
.class1{
color:red;
}
#id1{
color:blue;
}
</style>
</head>
<body>
<h1 id="id1" class="class1">Hello,World!</h1>
</body>
</html>

在这段实例中,“Hello,World!”的颜色究竟是红色还是蓝色?╰(‵□′)╯

浏览器到底应该听谁的?

事实上,早在1996时的CSS1的发布上,W3C就规定了一套规则,用来解决上面这段实例中的问题……

2.CSS特异性(Specificity)

基本定义与规则

CSS特异性(Specificity)是指浏览器**在解析 CSS 时,决定哪条样式规则将最终应用于元素的优先级计算系统。**或者说,可以把特异性想象成选择器的”权重分数”——分数高的选择器说了算q(≧▽≦q)!

一般地,我们用四级权重系统来表示特异性,格式为:(a, b, c, d)。对于权重系统,有以下规定:

等级 名称 表示方式 权重值 示例
A 行内样式 (1,0,0,0) 1000 style=”color: red;”
B ID 选择器 (0,1,0,0) 0100 #header
C 类/属性/伪类 (0,0,1,0) 0010 .class, :hover, [type]
D 元素/伪元素 (0,0,0,1) 0001 div, p, ::before
E 通配符 (0,0,0,0) 0000 *

当然,我们可以有更形象的比喻↓

来自《跟Wakaba酱一起学网站制作》书籍

针对于权重系统,有以下比较规则:

  1. 先比较a:行内样式数量多的胜出

  2. 如果a相同,比较b:ID选择器数量多的胜出

  3. 如果b相同,比较c:类/属性/伪类选择器数量多的胜出

  4. 如果c相同,比较d:元素/伪元素选择器数量多的胜出

  5. 如果都相同:后出现的规则胜出(后来居上原则)

让我们把之前提到的几个选择器来比较一下权重:

  1. p → 0个ID,0个类,1个元素 → (0,0,0,1)

  2. .highlight → 0个ID,1个类,0个元素 → (0,0,1,0)

  3. #main → 1个ID,0个类,0个元素 → (0,1,0,0)

如果它们三个都想给同一个段落上色,显而易见地:#main 的 (0,1,0,0) 胜出,因为它的b列(ID列)最大。

让我们再思考一个问题:

1
2
3
4
5
6
7
8
9
/* styleA */
div div div div div div div div div div div {
color: red; /* 十一个div元素选择器!∑( 口 || */
}

/* styleB */
.class1{
color: blue; /* 一个类选择器 */
}

在这段示例中,如果这个标签:<div class="class1">中所包裹的元素同时被styleA和styleB来选中,那么它的颜色会是什么?(请先自己思考)o_o ….

我们可以简单计算一下:根据特异性规则可得:

  • styleA:0个ID,0个类,11个元素 → (0,0,0,11)

  • styleB:0个ID,1个类,0个元素 → (0,0,1,0)

根据比较规则,显而易见,a b列都是0,持平。比c列,styleA是0,styleB是1,(0,0,1,0)胜出!

所以最终颜色是蓝色。

这个问题揭示了特异性的一个核心:容易看出来,一个“类“的权重,永远高于无数个“元素”的叠加。由此可以容易看出来,权重比较比的不是数量,而是等级。(和正常人类计算规则的“进制”不同)ヾ(^▽^*)))

不过,当选择器组合使用时,它的特异性是各部分相加,所以在遇到这种情况时,我们需要准确地分辨出各个选择器的种类,并把它们相加,同时,这也是最容易让人迷惑的地方(((φ(◎ロ◎;)φ))),我们多看几个例子。

1
2
3
4
5
6
7
8
9
/* styleC */
#nav .list li a{
color: green; /* ID + 类 + 元素 + 元素 */
}

/* styleD */
div#nav ul.list li a{
color: orange; /* 元素 + ID + 元素 + 类 + 元素 + 元素 */
}

在这段示例中,styleC和styleD哪个权重更大一些呢(._. )>?

容易计算到:

  • styleC:1个ID (#nav),1个类 (.list),2个元素 (li, a) → (0,1,1,2)

  • styleD:1个ID (#nav),1个类 (.list),4个元素 (div, ul, li, a) → (0,1,1,4)

根据比较规则,显然,在d中,4>2。

所以styleD的权重要大一些。

特殊规则

CSS在不断进化,新的功能性伪类带来了更灵活的写法,也带来了特异性计算的新规则。 o(* ̄▽ ̄*)o

通配符

通配符选择器 *权重为 (0,0,0,0),不影响特异性计算。

比如说:

1
2
3
4
5
6
*{
color:blue;
} /* (0,0,0,0) */
.class1 *{
color:blue;
} /* (0,0,1,0) */

组合符

组合符(>, +, ~, 空格)本身不增加权重。

比如说:

1
2
3
4
5
6
h2 span{
color:blue;
} /* (0,0,0,2) */
h2>span{
color:blue;
} /* (0,0,0,2) */

:not() 伪类

:not() 伪类本身不计权重,但其括号内的选择器正常计算

比如说:

1
2
3
4
5
6
7
8
9
:not(.disabled) {
color:blue;
} /* 计算 .disabled: (0,0,1,0)*/
:not(#id .class) {
color:blue;
} /* 计算 #id .class: (0,1,1,0) */
:not(p) {
clolor:blue;
} /* 计算 p: (0,0,0,1)*/

:is()伪类

:is():取参数列表中特异性最高的那个。

比如说

1
2
:is(#nav, .main, div)p{ color: red;}
/* 即使#nav在列表里,整个:is()的特性取#nav的(0,1,0,0),再加上p的(0,0,0,1),最终为(0,1,0,1) */

:where()伪类

:where():“权重归零器”!(o゚v゚)ノ无论括号里写什么,整个 :where() 部分的特异性为 (0,0,0,0)。

比如说:

1
2
:where(#nav, .main p{ color: blue;}
/* 整个:where()部分权重为0,只剩下p的(0,0,0,1)。 */

我们再来看一个例子:

1
2
3
4
5
6
7
8
9
/* styleE */
:where(#header) .title{
color: purple; /* :where()里的ID不算数!(○´・д・)ノ */
}

/* styleF */
h1.title{
color: black; /* 元素 + 类 */
}

此时title会显示什么颜色呢?

根据特异性规则,可以得到:

  • styleE::where()权重为0,加上 .title 的 (0,0,1,0) → (0,0,1,0)

  • styleF:1个元素 (h1),1个类 (.title) → (0,0,1,1)

根据比较规则,显而易见,a b都是0,c均为1,持平。比d,styleE是0,styleF是1。(0,0,1,1) 胜出!

所以说,此时title会显示黑色。

特异性完全相同时的特殊类型

如果特异性完全一样呢?浏览器会遵循层叠的另一个核心原则:顺序靠后的胜出。(‾◡◝)

比如说:

1
2
3
4
5
6
.class1{ 
background: blue;
} /* 写在前面 */
.class1{
background: red;
} /* 写在后面,覆盖前面的蓝色 */

!important

!important 声明会打破特异性体系。它会让一条样式声明获得最高优先级,但滥用会导致特异性体系崩溃,让代码变成一坨史山。只有在用尽其他方法都无法解决时,才考虑 !important,并且一定要加上注释说明原因。否则的话,只要过了一宿你就会不知道自己究竟写了什么东西。>﹏<

而且,若多个 !important冲突,则会回归特异性比较。

1
2
3
4
5
6
p{ 
color: blue !important;
} /* 无视特异性,直接应用 */
#id{
color: red !important;
} /* 若冲突,比较特异性 */

一般的前端开发中对于选择器的使用原则

当然,如上文所说,在一般的的前端开发中,不会遇到这些乱七八糟的选择器粘在一起让你算权重的情况,毕竟这就像你去711买个饭团,结果非要掏出计算器用微积分算它的热量分布一样,属于是把简单问题复杂化的行为呢 。

但有几条约定俗成的部分需要我们了解一下。

BEM命名法

BEM命名法核心思想就是:只用类选择器,通过命名规范来模拟层级关系,而不是用CSS的后代选择器来创建层级。

我们先来看一段实例

1
2
3
4
<div class="card">
<h2 class="title">标题</h2>
<button class="btn">点击</button>
</div>

如果用常规思维,我们可能会写:

1
2
.card .title { font-size: 20px; }  
.card .btn { color: blue; }

但BEM的做法是,把所有样式都绑定在单个类名上

1
.card__title { font-size: 20px; }   .card__btn { color: blue; }          

显然我们能看出来,这样几乎永远不需要用 ID,也永远不需要超过两层的嵌套。整个项目的特异性都一样的的,不存在谁意外超过谁的问题。

而且,类名本身即文档——看到 .card__title 就知道它是card块里的 title元素,不需要翻HTML结构去理解选择器含义。q(≧▽≦q)

单一类选择器优先原则

尽量或者只能用类选择器,禁止用ID和标签选择器来定义核心样式。

  • ID  因为其极高的特异性 ,一旦在CSS中使用,后续任何覆盖都必须用更高的特异性或!important。因此ID主要留给JS使用,不应该出现在样式表里

  • 标签选择器 ,它们会影响所有同标签元素。通常在样式重置或基础排版层才会少量使用(例如设定全局字体、基础行高),组件级别的样式一律不用裸标签选择器。

不要过度嵌套选择器

这样会变得非常难以理解,特异性不但会随着每一层嵌套叠加,而且会影响性能`(*>﹏<*)′

善用现代选择器

现代CSS提供了很多工具,让我们可以用更低特异性、更简洁的方式实现过去需要嵌套或叠加选择器才能做到的事,如上文提到过 :where() :is() :not()等。

尾……

嘛,说到底,

CSS的世界也没有那么难懂吧。(´▽`)

所以呢,下次再遇到样式冲突,

别急着掏用!important。

先静下心,打开开发者工具

像解谜一样看看那些 (a, b, c, d) 的数字

你会发现,

CSS其实还是很讲道理的吧。

学会了这些,

才是真正能掌控了样式的技巧呢。

(笑

About this Post

This post is written by Leapan, licensed under CC BY-NC-SA 4.0.