CSS选择器与优先级

其实平时用得多的选择器无非也就是那么几个,时间久了,许多不常用的选择器就慢慢忘记了。为了不让自己忘记这些选择器,今天就来对CSS的选择器做一个大致的整理和总结吧~

选择器分为基本选择器、组合选择器、伪类选择器和伪元素。其中,基本选择器包括元素选择器、类选择器、ID选择器、通配选择器、属性选择器;组合选择器包括相邻兄弟选择器、通用兄弟选择器、子选择器、后代选择器;伪类选择器包括目标伪类选择器、结构伪类选择器、动态伪类选择器、状态伪类选择器、语言伪类选择器、否定伪类选择器等 ;伪元素包括::before、::after、::first-letter、::first-line等。

下面,我将分别介绍这些选择器,并对优先级做一个大致总结。

文章结构:

  • 基本选择器
    1. 通配选择器
    2. 属性选择器
  • 组合选择器
    1. 相邻兄弟选择器
    2. 通用兄弟选择器
  • 伪类选择器
    1. 目标伪类选择器
    2. 结构伪类选择器
    3. 动态伪类选择器
    4. 状态伪类选择器
    5. 语言伪类选择器
    6. 否定伪类选择器
  • 伪元素
    1. ::before、::after
    2. ::first-letter
    3. ::first-line
  • 优先级

一、基本选择器

基本选择器包括元素选择器、类选择器、ID选择器、通配选择器、属性选择器。其中,元素选择器、类选择器、ID选择器是我们最常用的几个选择器,很简单,也没有需要注意的地方,我就不再写啦 ,重点来说说通配选择器和属性选择器。

1、通配选择器

一个 *** 就是一个通配选择器,可以匹配任意类型的HTML元素。

在CSS3中,* 可以和命名空间组合使用:

  • *:会匹配所有元素;
  • ns *:会匹配ns命名空间下的所有元素;
  • *** xx:会匹配所有命名空间下的xx元素,即xx{...} 。

不推荐使用通配选择器,因为它是性能最低的一个CSS选择器。

2、属性选择器

属性选择器通过已经存在的属性名或属性值匹配元素。

  • [attr]
    表示带有attr属性的元素;

  • [attr=value]
    表示带有attr属性、并且属性值为"value"的元素;

  • [attr~=value]
    表示带有attr属性的元素,并且该属性是一个以空格作为分隔的值列表,其中至少有一个值为"value";

  • [attr|=value]
    表示带有attr属性、并且属性值为"value"或以"value-"为前缀的元素;典型的应用场景是用来匹配语言简写代码(如zh-CN,zh-TW可以用zh作为value);
    注意点:attr属性中要匹配的"value"或"value-"前缀必须放在属性值的开头,否则不生效。

  • [attr^=value]
    表示带有attr属性、并且属性值以"value"开头的元素;
    注意点
    1、attr属性中要匹配的"value"必须放在属性值的开头,否则不生效;
    2、与 [attr|=value] 不同的地方在于,[attr|=value] 匹配的是"value-"前缀,而[attr^=value]只需以"value"开头即可,因此条件相对宽松。

  • [attr$=value]
    表示带有attr属性、并且属性值以"value"结尾的元素;
    注意点:可以有多个属性值,只匹配最后一个的结尾。

  • [attr=value]*
    表示带有attr属性、并且属性值为"value"的元素;

  • [attr operator value i]
    在带有属性值的属性选择器表达式的右括号(])前添加用空格间隔开的字母i(或者大写I)可以忽略属性值的大小写(ASCII字符范围内的字母)。如[attr*=value i],只需在上面的属性选择器表达式最后写"i"即可忽略大小写的检查。


二、组合选择器

组合选择器包括相邻兄弟选择器、通用兄弟选择器、子选择器、后代选择器。其中,子选择器和后代选择器只需要注意“儿子”和“后代”的关系,很简单,我这里就略过啦,重点来说相邻兄弟选择器和通用兄弟选择器。

1、相邻兄弟选择器

相邻兄弟选择器只会匹配紧跟其前方元素的同胞元素。
语法:前方元素+目标元素{ 样式声明 }
注意点:样式声明只针对目标元素,前方元素样式不变。

2、通用兄弟选择器

在使用 ~ 连接两个元素时,它会匹配第二个元素,条件是它必须跟(不一定是紧跟)在第一个元素之后,且他们都有一个共同的父元素。
语法:元素~元素{ 样式声明 }
注意点
1、样式声明只针对后方元素,前方元素样式不变;
2、这两个元素必须要有共同的父元素。


三、伪类选择器

在MDN文档中,有三十多个伪类选择器,可以大致分类为目标伪类选择器、结构伪类选择器、动态伪类选择器、状态伪类选择器、语言伪类选择器、否定伪类选择器这六种。

1、目标伪类选择器

1)、:target

:target用来指定那些包含片段标识符的URI的目标元素样式。
语法:元素:target{ 样式声明 },选择一个特定目标元素;
   :target{ 样式声明 },定位所有目标元素。
举例:

// html
<body>
  <a href="#d1">指向d1</a>
  <a href="#d2">指向d2</a>
  <div id='d1'></div>
  <div id='d2'></div>
</body>
// css
a{
  text-decoration:none;
}
div{
  width:300px;
  height:200px;
  border:1px solid ;
}
/* 对所有目标元素都生效 */
:target{
  border:none;
} 
/* 仅对于特定目标元素生效 */
#d1:target{
  background:#ddd;
}
#d2:target{
  background:pink;
}

结果:


通过上述例子我们可以看出,:target对任何目标元素都生效,所以点击“指向d1”时id为d1的div的border消失,点击“指向d2”时id为d2的div的border消失;而#d1:target、#21:target仅针对特定的目标元素。

2、结构伪类选择器

1)、:root

:root匹配文档树的根元素。应用到HTML,:root即表示为<html>元素,除了优先级更高外,相当于html标签选择器。
语法::root{ 样式声明 }。声明全局CSS变量时,:root很有用。

2)、:first/last-child

:first/last-child匹配父元素的所有子元素中第一个/最后一个元素。
语法:条件元素:first/last-child{ 样式声明 }。
注意点
1、语法 “ 条件元素:first/last-child{ 样式声明 } ” 实际需要满足两个条件才能进行 “ {} ” 内的样式渲染:第一,要找到所有的条件元素;第二,所有条件元素的第一个条件元素必须是父元素的第一个子元素,所有条件元素的最后一个条件元素必须是父元素的最后一个子元素
举例:

// html
<body>
  <div>
    <p>这是第1段</p>
    <p class='spec'>这是第2段</p>
    <p>这是第3段</p>
    <p>这是第4段</p>
    <p class='spec'>这是第5段</p>
    <p class='spec'>这是第6段</p>
  </div>
</body>
// css
p.spec:first-child{
  background:#ddd;
}
p.spec:last-child{
  background:pink;
}

结果:


我们看到,第一段没有生效,最后一段生效了。因为第一个p.spec不是父元素的第一个子元素,所以没有生效;而最后一个p.spec是父元素的最后一个子元素,所以生效了。

2、:first/last-child在使用时必须有一个父包含块,否则:last-child可能不生效。举例:

// html
<body>
  <p>这是第1段</p>
  <p>这是第2段</p>
  <p>这是第3段</p> 
  <p>这是第4段</p>
  <p>这是第5段</p>
  <p>这是第6段</p>
  <p>这是第7段</p>
</body>
// css
p:first-child{
  background:#ddd;
}
p:last-child{
  background:pink;
}

我们看到,第一个p生效,最后一个p未生效。现在我们给这些p元素包裹一个父元素:

// html
<body>
  <div>
    <p>这是第1段</p>
    <p>这是第2段</p>
    <p>这是第3段</p>
    <p>这是第4段</p>
    <p>这是第5段</p>
    <p>这是第6段</p>
    <p>这是第7段</p>
  </div>
</body>

结果:


3)、:first/last-of-type

:first/last-of-type匹配父元素的所有该子元素类型的第一个/最后一个元素。
语法:条件元素:first/last-of-type{ 样式声明 }
注意点
1、语法 “ 条件元素:first/last-of-type{ 样式声明 } ” 实际需要满足两个条件才能进行 “ {} ” 内的样式渲染:第一,要找到所有相同类型的条件元素;第二,对于每一种相同的类型,所有条件元素的第一个条件元素必须是父元素下该子元素类型的第一个子元素,所有条件元素的最后一个条件元素必须是父元素下该子元素类型的最后一个子元素。(注意与:first/last-child的区别)
举例:

// html
<section>
  <p>这是第1段</p>
  <p class='spec'>这是第2段</p>
  <div class='spec'>这是第一个div</div>
  <p>这是第3段</p>
  <p>这是第4段</p>
  <p>这是第5段</p>
  <p class='spec'>这是第6段</p>
  <div class='spec'>这是第二个div</div>
  <div>这是最后一个div</div>
</section>
// css
.spec:first-of-type{
  background:#ddd;
}
.spec:last-of-type{
  background:pink;
}

结果:



首先我们看到,有两种类型的条件元素,分别为p、div。第一个p元素没有spec类,所以 “ .spec:first-of-type{ background:#ddd; } ” 不生效,最后一个p元素有spec类,所以 “ .spec:last-of-type{ background:pink; } ” 生效;第一个div元素有spec类,所以 “ .spec:first-of-type{ background:#ddd; } ” 生效,最后一个div元素没有spec类,所以 “ .spec:last-of-type{ background:pink; } ” 不生效。

2、:first/last-of-type在使用时不需要父包含块。

4)、:nth-child/of-type、:nth-last-child/of-type

nth-child(an+b)可以匹配多个子元素,且子元素的顺序与模式an+b匹配。第一个子元素的位置为1。
语法:元素:nth-child(an+b){ 样式声明 }
注意点::nth-child的()中除了an+b外,还可以为odd(奇数,同2n+1)、even(偶数,同2n)。

:nth-last-child的用法同:nth-child,只不过它从后往前选择元素,而:nth-child从前往后选择元素。

:nth-of-type、:nth-last-of-type用法同:nth-child、:nth-last-child,但是必须是同类型子元素。

5)、:only-child、only-of-type

:only-child代表了属于某个父元素的唯一一个子元素。
:only-of-type代表了任意一个元素,这个元素没有其他相同类型的兄弟元素。
语法:元素:only-child/of-type{ 样式声明 }

6)、:empty

:empty可以在任何没有子元素的元素上起作用。只有元素节点和文本(包括空格)在“子元素”这个概念的考虑范畴之内,注释、运行指令不考虑。
语法:元素:empty{ 样式声明 }
举例:

// html
<div class="box"><!-- I will be pink --></div>
<div class="box">I will be silver</div>
<div class="box">
   <!-- I will be silver because of the whitespace around this comment -->
</div>
// css
.box {
  background: #c0c0c0;
  height: 200px;
  width: 200px;
  border:1px solid;
  float:left;
}
.box:empty {
  background: pink;
}

结果:


3、动态伪类选择器

1)、:active

:active 匹配被用户激活的元素。它让页面能在浏览器检测到激活时给出反馈。当用鼠标交互时,它代表的是用户按下按键和松开按键之间的时间。:active伪类通常用来匹配tab键交互。通常用于但不限于a标签和button标签。
语法:元素:active{ 样式声明 }
注意点
1、:active代表的是用户按下按键和松开按键之间的时间,时间很短,长按鼠标即可保持效果;
2、:active可能会被后声明的其他链接相关的伪类覆盖,这些伪类包括:link、:hover和:visited。为了正常加上样式,需要把:active的样式放在所有链接相关的样式之后,这种链接伪类先后顺序被称为LVHA顺序::link—:visited—:hover—:active。举例:

    // html
    <p>
      这个链接在鼠标按下和松开的这段时间内会变成红色: 
      <a href="#">Mozilla Developer Network</a>.
    </p>
// css
/* 未访问链接 */
a:link { 
  color: black 
} 
/* 已访问链接 */
a:visited { 
  color: purple 
} 
/* 用户鼠标悬停 */
a:hover { 
  font-weight: bold 
}
/* 激活链接 */
a:active { 
  color: red 
} 

我们长按鼠标可以很清楚地看到效果:a标签内的文字变为了红色。
现在我们改变CSS中:active的顺序,如:

// css
/* 未访问链接 */
a:link { 
color: black 
} 
/* 激活链接 */
a:active { 
color: red 
} 
/* 已访问链接 */
a:visited { 
color: purple 
} 
/* 用户鼠标悬停 */
a:hover { 
font-weight: bold 
}

我们长按鼠标时,文字却不是红色的。

总结::active可能会被后声明的其他链接相关的伪类覆盖,所以为了正常加上样式,需要遵循LVHA顺序,把:active的样式放在所有链接相关的样式之后。

2)、:hover、:link、:visited

:hover可以在任何元素上使用。与:active类似,:hover会被任何与链接相关的伪类重写,如:link、:visited、:active等。为了确保生效,:hover规则需要放在:link和:visited规则之后,但是在:active之前,遵循LVHA顺序,即:link—:visited—:hover—:active。
语法:元素:hover{ 样式声明 }

:link、:visited用法同:hover,不再赘述。

3)、:focus

:focus在一个元素成为焦点时生效,用户可以通过键盘或鼠标激活焦点。
语法:元素:focus{ 样式声明 }

4、状态伪类选择器

1)、:enabled、:disabled

:enabled表示任何启用的(enabled)元素。如果一个元素能够被激活(如选择、点击或接受文本输入)或获取焦点,则该元素是启用的。元素还有一个禁用的状态(disabled),在被禁用时,元素不能被激活或获取焦点。
语法:元素:enabled{ 样式声明 }
   元素:disabled{ 样式声明 }

2)、:checked

:checked用于radio、checkbox、option这三个标签的被选中元素。
语法:元素:checked{ 样式声明 }
举例:

//html
<body>
  <input type="radio" name='test' value='A'><span>A</span>
  <input type="radio" name='test' value='B'><span>B</span>
  
  <input type="checkbox" name='test' value='A'><span>A</span>
  <input type="checkbox" name='test' value='B'><span>B</span>
  
  <select name="test" id="">
    <option value="A">A</option>
    <option value="B">B</option>
    <option value="C">C</option>
  </select>
</body>
// css
input[type="radio"]:checked+span {
  color:red;
}
input[type="checkbox"]:checked+span{
  color:red;
}
select[name="test"]>option:checked{
  color:red;
}

结果:


3)、:required、:optional

:required表示任意拥有required属性的<input>或<textarea>元素使用它。它允许表单在提交之前容易地展示必选字段并且渲染其外观。
:optional表示任意没有required属性的<input>或<textarea>元素使用它。它允许表单容易地展示可选字段并且渲染其外观。
语法:元素:required{ 样式声明 }
   元素:optional{ 样式声明 }

4)、:valid、:invalid

:valid表示任何其内容可以根据设置的输入类型正确地验证<input>或<form>元素,有助于用户确认其数据格式是否正确。
:invalid表示任何<input>或<form>元素的内容无法通过输入的类型设置的验证。这使您可以轻松设置无效字段的外观,帮助用户设别并更正错误。
语法:元素:valid{ 样式声明 }
   元素:invalid{ 样式声明 }
举例:

// html
<input type="email">
// css
input:valid{
  outline:0;           
  color:green;
}
input:invalid{
  outline:0;          
 color:red;
}

结果:


5)、:placeholder-shown

:placeholder-shown适用于所有有placeholder属性的元素。
语法:元素:placeholder-shown{ 样式声明 }

6)、:read-only、:read-wirte

:read-only表示不可被编辑的元素。
:read-wirte表示可以被编辑的元素。
语法:元素:read-only{ 样式声明 }
   元素:read-wirte{ 样式声明 }
举例:

// html
<input type="text" value='Type whatever you want here.'>
<input type="text" value='This is a read-only field.' readonly>
// css
input:read-only{
  background:#ddd;
}
input:read-write{
  background:lightcyan;
}

结果:


7)、:in-range、:out-of-range

:in-range表示不可被编辑的元素。
:out-of-range表示可以被编辑的元素。
语法:元素:in-range{ 样式声明 }
   元素:out-of-range{ 样式声明 }
举例:

// html
<input type="number" placeholder='1 to 10' min='1' max='10' value='0'>
// css
input:in-range{
  color:blue;
}
input:out-of-range{
  color:red;
}

结果:


5、语言伪类选择器

1)、:lang

在HTML中,文档的语言由lang属性、<meta>标签、协议中携带的语言信息(比如HTTP协议头)共同决定。
语法:元素:lang(语言代码){ 样式声明 }
举例:

// html
<body>
  很高兴见到你,<span lang='en'>Caroline.</span>
</body>
 // css
span:lang(en){
  font-style: italic;
}

结果:


6、否定伪类选择器

1)、:not()

:not(X),是以一个简单的X选择器为参数的功能性标记函数。它匹配不符合参数选择器X描述的元素。X不能包含另外一个否定选择器。
语法:元素:not(选择器){ 样式 }
注意点
1、:not()伪类的优先级即为它参数选择器的优先级。:not()不像其它伪类,它不会增加选择器的优先级
2、:not(X)将匹配任何非X元素,包括html和body
3、利用这个伪类可以重写其它规则。例如,foo:not(bar)和foo将匹配同样的元素,但是前者的优先级更高。
4、这个选择器只会应用在一个元素上,你不能用它排除所有祖先元素。例如,body:not(table) a将依旧会应用在table内部的a标签上,因为<tr>将会被:not()这部分选择器匹配。
5、利用这个伪类可以写一个完全没有用处的选择器。例如,:not(*)匹配任何费元素的元素,因此这个规则永远不会被应用。
举例:

// html
<p>Some text.</p>
<p class="classy">Some other text.</p>
<span>One more text<span>
// css
p:not(.classy) { 
  color: red; 
}
body :not(p) { 
  color: blue; 
}

结果:


另,在打印文档时可以使用:first、:left、:right等伪类选择器进行文本样式设置。


四、伪元素

1、::before、::after

::before会为当前元素创建一个子元素作为伪元素。常通过content属性来为一个元素添加修饰性的内容。此元素默认为行内元素
::after用来匹配已选中元素的一个虚拟的最后子元素。常通过content属性来为一个元素添加修饰性的内容。此元素默认为行内元素
语法:元素::before{ content:'xxx'; ...... }
   元素::after{ content:'xxx';...... }
注意点
1、::before、::after创建的元素是该元素的子元素。所有有子元素的标签(如input标签就没有子元素)都默认有这两个子元素。
2、::before、::after创建的元素无法用鼠标选中!!
举例:

// html
<span>今天天气很好。</span>
我也觉得天气很好。
// css
span::before{
  content:'周一';
  background:#ddd;
  font-weight:bold;
}
span::after{
  content:'是的';
  display:block;
  background:pink;
  color:red;
  font-weight:bold;
}

结果:


我们看到,“周一”和“是的”这两个伪元素无法用鼠标选中。

2、::first-letter

::first-letter会选中一整块文字第一行的第一个字母。
语法:元素::first-letter{ 样式声明 }
注意点:首行只有在block-container box内部才有意义,因此,::first-letter伪元素只在display属性值为block、inline-block、table-cell、list-item或者table-caption的元素上才起作用,其他情况下,::first-letter毫无意义。

3、::first-line

::first-line只应用于一个元素的首行。
语法:元素::first-line{ 样式声明 }
注意点:::first-line只能在块容器中,因此,::first-letter伪元素只在display属性值为block、inline-block、table-cell、list-item或者table-caption的元素上才起作用,其他情况下,::first-line不起作用。


五、优先级

优先级就是分配给指定的CSS声明的一个权重,它由匹配的选择器中的每一种选择器类型的数值决定。当同一个元素有多个声明时,优先级最高的声明将会被应用到元素上。下面我们就来看看这个权重该怎样计算。

CSS样式选择器分为四个等级,从高到低依次为a、b、c、d:

  • 如果样式是行内样式(通过style="..."定义),则a=1;
  • b为ID选择器的总数
  • c为class类选择器(例如:.example)、属性选择器(例如:[type="radio"])、伪类选择器(例如:::hover)的总数
  • d为元素选择器(例如:h1)和伪元素选择器(例如::before)的总数

一些例子:

选择器 权重(a,b,c,d)
style='xxx' 1,0,0,0
#wrapper #content {} 0,2,0,0
#content .example{} 0,1,1,0
div#content {} 0,1,0,1
#content p {} 0,1,0,1
#content {} 0,1,0,0
p.comment .example{} 0,0,2,1
div.comment p {} 0,0,1,2
.comment p {} 0,0,1,1
p.comment {} 0,0,1,1
.comment {} 0,0,1,0
div p {} 0,0,0,2
p{} 0,0,0,1

上面的例子中,优先级依次递减。我们可以把这个“权重”理解为十进制数字,数字越大,则优先级越高。

注意点
1、通配选择器(*)、组合选择器(例如:+、~、>)和否定伪类(:not())不会影响优先级(但是,:not()内部声明的选择器会影响优先级)。
2、!important优先级最高,高于行内样式。
举例:

// html
<body>
  <div class="parent"> 
    <span class="child" style='color:yellow;'>Caroline,</span> 
    <span>nice to meet you.</span> 
  </div>
</body>
// css
.parent>span{
  color:red !important;
}
span.child{
  color:blue;
}

结果:


如果不添加!important,style='color:yellow;'的优先级高于span.child,span.child又高于.parent>span,所以行内样式应该生效,字体应该变成黄色;给.parent>span添加!important后,实际上“破坏”了CSS的优先级规则。所以!important应尽量避免使用!

3、优先级是基于选择器的形式进行计算的。
举例:

// html
<body>
  <p id="foo">I am a sample text.</p>
</body>
// css
* #foo {
  color: green;
}
*[id="foo"] {
  color: purple;
}

结果:


在上面的例子中,尽管选择器[id="foo"]选择了一个ID,但它还是作为一个属性选择器来计算自己的优先级。

4、无视DOM树中的距离。
举例:

// html
<body>
  <h1>Here is a title!</h1>
</body>
// css
body h1 {
  color: green;
}
html h1 {
  color: purple;
}

结果:


优先级总结:
1、按照权重来确定优先级;
2、优先级相同时,后面的会覆盖前面;
3、!important优先级最高。


关于CSS选择器和优先级就先写到这里,总的来说还是比较全面的。这部分不难,就是内容挺多,需要经常回顾,免得自己忘了。

由于个人水平有限,博客错误之处,烦请指正!

参考资料:
1、MDN:CSS选择器
2、MDN:优先级
3、CSS选择器优先级

推荐阅读更多精彩内容