Published on

CSS选择器 & 优先级

Authors
  • avatar
    Name
    青雲
    Twitter

CSS选择器是前端开发中不可或缺的部分,它允许开发者精确控制页面中元素的样式。选择器的正确使用关乎网页的美观、用户体验以及维护的方便性。了解CSS选择器的种类以及优先级能够帮助我们写出更清晰、更高效的代码。

CSS选择器简介

CSS选择器定义了应当匹配哪些元素,并对这些元素应用定义的样式。根据选择器的不同,可以选择一个元素、一组元素,或者某些特定情况下的元素。 CSS提供了多种选择器,大致可以分为以下几类:

基本选择器

  • 类型选择器:通过节点名称匹配元素。
p {
  color: blue;
}
  • 类选择器:(如.classname)按CSS类名匹配元素。
.classname {
  font-weight: bold;
}
  • ID选择器:(如 #idname)使用ID属性选择单一元素。
#uniqueId {
  background-color: yellow;
}
  • 通配选择器:(如 *)匹配所有元素。
* {
  box-sizing: border-box;
}

属性选择器

  • 选择具有特定属性或属性值的元素
input[type="text"] {
  border: 1px solid gray;
}

伪类选择器

  • 动态伪类(如 :hover)根据用户与元素的交互状态选择元素。
  • 目标伪类(如 :target)选择当前活动的目标元素。
  • 语言伪类(如 :lang())基于元素的语言进行选择。
a:hover {
  text-decoration: underline;
}

伪元素选择器

  • ::before 和 ::after 在元素内容前后插入内容。
  • ::first-line 和 ::first-letter 分别用于选择元素的第一行或第一个字母。
p::first-line {
  font-variant: small-caps;
}

组合选择器

  • 后代选择器(如 div span)选择某元素内部的所有特定后代元素。
  • 子选择器(如 div > p)仅选择直接子元素。
  • 相邻兄弟选择器(如 h1 + p)选取紧接在另一元素后的元素。
  • 后续兄弟选择器(如 h1 ~ p)选取同一父元素下的所有特定兄弟元素。
div span {
  color: green;
}

div > p {
  list-style-type: none;
}

h1 + p {
  margin-top: 0;
}

h1 ~ p {
  color: red;
}

选择器优先级

选择器的优先级是根据它的特殊性计算的。每个选择器都会根据其类型被赋予一个“权重”,并按照特定的计算规则进行累加,形成一个权重值,这个值将决定最终样式的应用。 特定性值是一个由四个部分组成的值,通常不作为实际的数字,而是作为逗号分隔的四个不同的部分来理解,例如 (0, 1, 0, 0)。

  • 样式规则的权重:
    • 内联样式(如在HTML元素的 style 属性内定义的样式)算作 (1,0,0,0)。
    • 每个ID选择器增加 (0,1,0,0)。
    • 每个类选择器、属性选择器或伪类增加 (0,0,1,0)。
    • 每个类型选择器或伪元素增加 (0,0,0,1)。
  • 样式声明中 !important 的作用:
    • 声明中加上 !important 会覆盖上述任何具体性计算结果,迫使样式优先应用。
  • 继承和默认优先级:
    • 继承得到的样式通常有较低的优先级。
    • 用户代理(浏览器)的默认样式通常优先级最低。

假设我们有如下HTML代码:

<div id="toolbar">
      <!-- E: 选择器内联样式,权重为 (1,0,0,0) -->
      <button class="button active" style="background-color: orange">
        Click me!
      </button>
    </div>

对应的CSS样式如下:

/* A: 选择器类型选择器,权重为 (0,0,0,1) */
button {
  background-color: blue;
  color: white;
}

/* B: 选择器类选择器,权重为 (0,0,1,0) */
.button {
  padding: 10px 20px;
  font-size: 14px;
}

/* C: 选择器类伪类,权重为 (0,0,2,0) */
button.active {
  background-color: green;
}

/* D: 选择器ID选择器和类选择器,权重为 (0,1,1,0) */
#toolbar .button {
  border: 10px solid red;
}

现在我们来计算每个选择器的优先级来决定最终button元素的样式:

选择器特殊性特殊性值(简写)样式
A: button(0,0,0,1)(1)**background-color: blue;
color: white;**
B: .button(0,0,1,0)(10)**padding: 10px 20px;
font-size: 14px;**
C: button.active(0,0,1,1)(11)background-color: green;
D: #toolbar .button(0,1,1,0)(110)border: 1px solid red;
E: (inline)(1,0,0,0)(1000)background-color: orange;

根据上述分析可得:

  • background-color: 由内联样式选择器E决定,因为它有最高的特殊性。
  • color: 除了选择器A,没有其他选择器应用 color 属性,因此最终颜色是 white。
  • padding 和 font-size: 由选择器B决定,因为没有其他选择器应用这些属性。
  • border: 由选择器D决定,因为其特殊性高于其他选择器。

因此,button 元素的最终样式将会是: image.png

{
    background-color: orange; /* 来自内联样式 */
    color: white;             /* 来自选择器A */
    padding: 10px 20px;       /* 来自选择器B */
    font-size: 14px;          /* 同上 */
    border: 1px solid red;    /* 来自选择器D */
}

优先级的实际应用

在实际项目中,管理好样式优先级可以防止意外的样式覆盖和冲突。以下是一些常见的管理样式优先级的方法:

  1. 重置样式:通常在项目开始时应用全局样式重置(如使用 normalize.css 或 reset.css),建立一致的基线。
  2. 样式冲突解决:对于冲突,使用更具体的选择器来解决优先级问题,或者重新考虑选择器的结构和样式定义方式。
  3. 避免 !important:尽量避免使用 !important,因为它会破坏样式的自然层叠和优先级逻辑,创建难以维护的代码。

最佳实践

  • 编码规范:制定一致的编码规范和命名规范。
  • 合理的样式组织:组织样式表结构,使之按组件或功能模块化。
  • CSS预处理器:通过如Sass或Less等CSS预处理器,使用嵌套规则、变量和函数来灵活控制样式应用,简化选择器复杂度。

CSS选择器的性能影响

虽然影响微小,但选择器的复杂性确实可以影响到页面的渲染性能。以下是一些优化选择器性能的建议:

  • 尽可能使用简单的选择器。
  • 避免使用过多的子选择器和相邻选择器。
  • 有意识地组织样式表,避免重复选择器。
  • 使用开发者工具来审查和分析CSS性能。

排查CSS性能的常用方法

  • Performance面板
    • 打开Performance面板,点击录制按钮,然后重新加载页面或执行页面的特定操作。
    • 停止录制并查看记录的时间线,确定CSS相关的渲染事件。
    • 分析Layout和Paint,这两种活动通常与CSS渲染性能相关—Layout指的是计算元素几何位置的过程,而Paint是实际填充像素的过程。
  • Coverage面板
    • 使用命令菜单【ctrl+shift+p】打开菜单,输入coverage,选择coverage面板
    • 点击开始录制,重新加载页面,或者浏览网页以确保各种交互被覆盖。
    • 停止录制并查看结果,这将显示出多少字节的CSS资源未被使用。
  • 利用Elements面板
    • 在开发者工具中,选择“Elements”标签页,选择页面上的一个元素。
    • 在右侧面板中,切换到“Computed”标签。这里可以看到应用到选中元素上的所有计算样式,包括从哪些规则继承而来。
    • 右键点击特定的DOM元素,选择“Force state”可以模拟伪类如 :hover 的状态,查看这些状态变化是否导致性能问题。

面试实战

问题1 描述 !important 在CSS中的作用,并解释为什么通常不推荐使用?

答案:

!important 是一个CSS声明的后缀,它可以添加到样式属性的最后,来覆盖页面内正常的CSS层叠规则。任何标记为 !important 的样式声明,几乎都会覆盖同一元素上其他任何未标记为 !important 的声明。不推荐使用 !important 是因为它会破坏CSS的自然层叠规则,使得样式维护变得困难,尤其是在跨组件或者跨团队合作时,可能会发生意外的样式覆盖,并且增强了代码的脆弱性。

面试题 2: 以下哪个选择器的优先级最高?为什么?

  1. div#app
  2. #app
  3. body #app div
  4. div.container

答案:

  • div#app 的优先级最高。因为它既包含了ID选择器(权重为1),也包含了类型选择器(权重为1),所以它的具体性值是(0,1,0,1)。
  • #app 仅仅包含ID选择器,具体性值是(0,1,0,0)。
  • body #app div 包含一个ID选择器和两个类型选择器,具体性值是(0,1,0,2),但由于ID选择器具有较高的权重,这个选择器的优先级仍然低于 div#app
  • div.container 中包含一个类选择器和一个类型选择器,具体性值为(0,0,1,1),优先级最低。

问题3: 子元素会继承父元素的哪些CSS样式?继承的样式如何与CSS选择器特殊性相抗衡?

答案:

子元素会自然继承一些样式属性,例如 font-family, colorvisibility,但不会继承那些关于布局的属性,例如 border, margin, background-color 等。如果对子元素明确设置了样式,则该样式根据层叠顺序和特殊性规则优先应用。继承的样式具有最低的特殊性权重,等同于类型选择器,通常是 (0,0,0,0);除非被更高特殊性的CSS规则覆盖,否则继承的样式会被应用。

面试题 4: 什么是伪元素,它们与伪类有什么不同?

答案:

伪元素是用于添加装饰或特殊效果的选择器,它们表示元素的某个部分,如 ::before 和 ::after 可以用来在元素内容的前后添加内容。伪类则是反映元素在所处上下文中的特定状态的选择器,例如 :hover、:active 和 :visited。伪元素和伪类的主要区别在于它们的应用:伪元素创建元素的部分,而伪类描述元素的特定状态。

问题5: 当两个相同权重的CSS选择器应用于同一元素时,哪一个会生效?

答案:

如果两个选择器的特殊性完全相同,那么位于CSS文件较后位置的选择器将会生效,因为CSS的层叠规则是根据源码顺序来处理权重相同的规则的。这意味着最后解析的规则将覆盖先前相同权重的规则。