Skip to content

CSS 选择器优先级:层叠与权重的艺术

层叠的概念

CSS(Cascading Style Sheets)中的"Cascading"(层叠)是其核心机制。想象多个瀑布从不同高度倾泻而下,最终汇聚成一池水——这就是 CSS 的层叠原理。当多条规则应用于同一个元素时,浏览器需要决定哪条规则最终生效。

这个决策过程基于三个关键因素:

  1. 来源:样式来自哪里(浏览器默认、作者样式、用户样式)
  2. 特异性:选择器的具体程度
  3. 顺序:在代码中出现的位置

特异性计算规则

特异性(Specificity)是 CSS 优先级系统的核心。每个选择器都有一个特异性值,通常用四位数表示:(a, b, c, d)

计算规则

(a, b, c, d)
  • a: 内联样式(1000)
  • b: ID 选择器的数量(100)
  • c: 类选择器、属性选择器、伪类的数量(10)
  • d: 元素选择器、伪元素的数量(1)

让我们看具体示例:

css
/* (0, 0, 0, 1) - 权重: 1 */
p {
  color: black;
}

/* (0, 0, 1, 0) - 权重: 10 */
.text {
  color: blue;
}

/* (0, 0, 1, 1) - 权重: 11 */
p.text {
  color: green;
}

/* (0, 1, 0, 0) - 权重: 100 */
#header {
  color: red;
}

/* (0, 1, 1, 1) - 权重: 111 */
#header p.text {
  color: purple;
}

/* (1, 0, 0, 0) - 权重: 1000 */
/* 内联样式 */
<p style="color: orange;">Text</p>

复杂选择器计算

css
/* (0, 0, 1, 2) = 12 */
/* 1个类 + 2个元素 */
div p.highlight {
  color: blue;
}

/* (0, 0, 2, 1) = 21 */
/* 2个类 + 1个元素 */
.container .text.bold {
  color: red;
}

/* (0, 1, 1, 2) = 112 */
/* 1个ID + 1个类 + 2个元素 */
#main div.content p {
  color: green;
}

/* (0, 0, 3, 0) = 30 */
/* 3个类 */
.nav.primary.active {
  color: purple;
}

/* (0, 0, 2, 0) = 20 */
/* 1个属性选择器 + 1个伪类 */
input[type="text"]:focus {
  border-color: blue;
}

特殊规则

css
/* 通用选择器 * 的权重为 0 */
* {
  margin: 0; /* (0, 0, 0, 0) */
}

/* 组合器(>, +, ~, 空格)不增加权重 */
div > p {
  /* (0, 0, 0, 2) */
  color: blue;
}

.parent > .child {
  /* (0, 0, 2, 0) */
  color: red;
}

/* :not() 本身不计入权重,但括号内的选择器计入 */
div:not(.special) {
  /* (0, 0, 1, 1) - 1个元素 + 1个类 */
  color: green;
}

/* :is() :where() 的区别 */
:is(#header, .nav) p {
  /* (0, 1, 0, 1) - 取最高权重的ID */
  color: blue;
}

:where(#header, .nav) p {
  /* (0, 0, 0, 1) - :where()权重总是0 */
  color: red;
}

优先级规则

当特异性相同时,后面的规则会覆盖前面的规则:

css
/* 两者权重相同都是 (0, 0, 1, 0) = 10 */
.text {
  color: blue;
}

.text {
  color: red; /* 这个生效 */
}

来源优先级

从高到低的优先级顺序:

1. 用户代理重要声明(浏览器 !important)
2. 用户重要声明(用户自定义 !important)
3. 作者重要声明(开发者 !important)
4. 作者正常声明(开发者普通样式)
5. 用户正常声明(用户自定义普通样式)
6. 用户代理正常声明(浏览器默认样式)

实际开发中,我们主要关注作者样式的优先级:

css
/* 正常声明 */
p {
  color: blue;
}

/* !important 声明 - 优先级最高 */
p {
  color: red !important; /* 这个生效 */
}

/* 即使特异性更高,也无法覆盖 !important */
#content .text p {
  color: green; /* 不生效 */
}

!important 的正确使用

!important 是优先级系统中的"核武器",应该谨慎使用:

css
/* 不推荐:滥用 !important */
.button {
  background: blue !important;
  color: white !important;
  padding: 10px !important;
}

/* 推荐:只在必要时使用 */
.utility-class {
  display: none !important; /* 确保工具类总是生效 */
}

/* 覆盖第三方库的样式 */
.third-party-widget .custom-override {
  color: red !important;
}

合理使用场景

  • 覆盖内联样式
  • 覆盖第三方库的样式
  • 创建必须始终生效的工具类
  • 避免重构遗留代码时的风险

避免使用的场景

  • 作为解决优先级冲突的首选方案
  • 在常规组件样式中使用
  • 连续多个 !important 互相覆盖

继承机制

某些 CSS 属性会从父元素继承到子元素,但继承的属性没有任何特异性(甚至比 0 还低):

css
/* 可继承的属性 */
body {
  color: #333; /* 继承 */
  font-family: Arial; /* 继承 */
  line-height: 1.6; /* 继承 */
}

/* 不可继承的属性 */
div {
  border: 1px solid #ddd; /* 不继承 */
  padding: 20px; /* 不继承 */
  margin: 10px; /* 不继承 */
}

/* 即使特异性为0,也会覆盖继承的值 */
p {
  color: blue; /* (0,0,0,1) 覆盖继承的 color */
}

/* 强制继承 */
.child {
  color: inherit; /* 明确继承父元素的color */
  border: inherit; /* 明确继承父元素的border */
}

/* 恢复初始值 */
.reset {
  all: initial; /* 重置所有属性到初始值 */
}

/* 恢复为未设置状态 */
.unset {
  all: unset; /* 可继承属性继承,不可继承属性使用初始值 */
}

常见可继承属性

  • 文本相关:color, font-*, line-height, text-align, text-indent
  • 列表相关:list-style-*
  • 其他:cursor, visibility

常见不可继承属性

  • 盒模型:width, height, margin, padding, border
  • 定位:position, top, left
  • 布局:display, float
  • 背景:background-*

实战案例

案例 1:样式冲突解决

html
<div id="container" class="wrapper">
  <p class="text highlight">Hello World</p>
</div>
css
/* 分析权重 */
p {
  color: black; /* (0, 0, 0, 1) = 1 */
}

.text {
  color: blue; /* (0, 0, 1, 0) = 10 */
}

.wrapper p {
  color: green; /* (0, 0, 1, 1) = 11 */
}

#container p {
  color: red; /* (0, 1, 0, 1) = 101 */
}

.wrapper .text {
  color: purple; /* (0, 0, 2, 0) = 20 */
}

#container .text.highlight {
  color: orange; /* (0, 1, 2, 0) = 120 - 最终生效 */
}

案例 2:降低特异性

使用 :where() 降低权重:

css
/* 高权重基础样式 - 难以覆盖 */
#app .button {
  background: blue; /* (0, 1, 1, 0) = 110 */
}

/* 优化:使用 :where() 降低权重 */
:where(#app) .button {
  background: blue; /* (0, 0, 1, 0) = 10 */
}

/* 现在容易覆盖 */
.button.primary {
  background: red; /* (0, 0, 2, 0) = 20 - 可以覆盖 */
}

案例 3:模块化样式架构

css
/* 基础样式 - 低权重 */
.btn {
  padding: 10px 20px;
  border-radius: 4px;
}

/* 变体样式 - 中等权重 */
.btn.btn-primary {
  background: blue;
  color: white;
}

.btn.btn-secondary {
  background: gray;
  color: white;
}

/* 状态样式 - 高权重,使用 :is() 提高灵活性 */
.btn:is(:hover, :focus, :active) {
  transform: translateY(-2px);
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}

/* 特殊场景 */
.sidebar .btn {
  width: 100%;
}

案例 4:处理第三方库冲突

css
/* Bootstrap 的样式 */
.btn-primary {
  background-color: #007bff; /* Bootstrap 默认蓝色 */
}

/* 方法1:提高特异性 */
.my-app .btn-primary {
  background-color: #e74c3c; /* 自定义红色 */
}

/* 方法2:使用 !important(不推荐,但有时必需) */
.btn-primary {
  background-color: #e74c3c !important;
}

/* 方法3:使用自定义类覆盖 */
.btn-custom {
  background-color: #e74c3c;
}

调试技巧

Chrome DevTools

css
/* 在浏览器开发者工具中: */
/* 
1. 被划掉的样式 = 被更高优先级覆盖
2. 灰色的样式 = 不适用于该元素
3. 查看 "Computed" 标签查看最终生效的值
*/

特异性计算器

javascript
// 简单的特异性计算函数
function calculateSpecificity(selector) {
  const ids = (selector.match(/#/g) || []).length;
  const classes = (selector.match(/\./g) || []).length;
  const attrs = (selector.match(/\[/g) || []).length;
  const pseudoClasses =
    (selector.match(/:/g) || []).length - (selector.match(/::/g) || []).length;
  const elements = selector.split(/[#.\[\s>+~:]/).length - 1;

  return {
    ids: ids * 100,
    classes: (classes + attrs + pseudoClasses) * 10,
    elements: elements * 1,
    total: ids * 100 + (classes + attrs + pseudoClasses) * 10 + elements,
  };
}

// 使用
console.log(calculateSpecificity("#header .nav li.active"));
// { ids: 100, classes: 20, elements: 2, total: 122 }

最佳实践

1. 保持低特异性

css
/* 不推荐:过高的特异性 */
#page #content .sidebar .widget .title h3 {
  color: blue;
}

/* 推荐:简洁的选择器 */
.widget-title {
  color: blue;
}

2. 使用 BEM 命名规范

css
/* BEM(Block Element Modifier)降低特异性冲突 */
.card {
} /* Block */
.card__title {
} /* Element */
.card__title--large {
} /* Modifier */
.card--featured {
} /* Modifier */

3. 避免 ID 选择器

css
/* 不推荐 */
#header {
}

/* 推荐 */
.header {
}

4. 分层样式架构

css
/* 第1层:重置和基础 (权重: 1-10) */
* {
  box-sizing: border-box;
}
body {
  font-family: Arial;
}

/* 第2层:布局 (权重: 10-20) */
.container {
  max-width: 1200px;
}
.grid {
  display: grid;
}

/* 第3层:组件 (权重: 10-30) */
.button {
  padding: 10px;
}
.card {
  border-radius: 8px;
}

/* 第4层:工具类 (权重: 10-20) */
.text-center {
  text-align: center;
}
.mt-4 {
  margin-top: 1rem;
}

/* 第5层:状态 (权重: 20-40) */
.is-active {
  background: blue;
}
.has-error {
  border-color: red;
}

5. 文档化权重策略

css
/**
 * 权重规则:
 * 0-10:   基础样式和重置
 * 10-30:  组件样式
 * 30-50:  状态和变体
 * 100+:   避免使用,仅用于覆盖第三方库
 * !important: 仅用于工具类
 */

总结

理解 CSS 优先级是编写可维护样式表的关键。通过合理控制选择器的特异性,你可以创建灵活、易于扩展的样式系统,避免陷入优先级战争的泥潭。

核心要点

  • 层叠机制:CSS 通过来源、特异性、顺序三个因素决定最终样式
  • 特异性计算:(a, b, c, d) 系统
    • 内联样式:1000
    • ID 选择器:100
    • 类/属性/伪类:10
    • 元素/伪元素:1
  • 特殊选择器* 权重为 0,组合器不增加权重,:not() 不计入但内容计入,:where() 权重为 0
  • !important:谨慎使用,仅用于工具类或覆盖第三方库
  • 继承机制:部分属性可继承,继承值优先级最低
  • 调试技巧:浏览器开发者工具、特异性计算器
  • 最佳实践:保持低特异性、使用 BEM、避免 ID、分层架构、文档化策略