CSS(Cascading Style Sheets)是前端开发的核心技术之一。要写出可维护、高性能的样式,就必须深刻理解三个关键概念:选择器(Selectors)、层叠(Cascade) 与 继承(Inheritance)。本文将从零开始,系统性地深入每一个细节,配合大量可运行的代码示例,帮助你彻底掌握 CSS 的核心机制。
一、CSS 选择器大全
CSS 选择器用于定位 HTML 文档中的元素并对其应用样式。按照复杂度,我们可以将它们分为六大类。
1.1 基本选择器
基本选择器是 CSS 的基石,包括四种:
| 类型 | 语法 | 示例 | 说明 |
|---|---|---|---|
| 元素选择器 | element |
div {} |
选中所有 <div> 元素 |
| 类选择器 | .class |
.highlight {} |
选中所有 class="highlight" 的元素 |
| ID 选择器 | #id |
#header {} |
选中 id="header" 的唯一元素 |
| 通配符选择器 | * |
* {} |
选中页面中所有元素 |
示例代码:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<style>
/* 元素选择器 */
p { color: #333; font-size: 16px; }
/* 类选择器 */
.warning { color: #d9534f; font-weight: bold; }
/* ID 选择器 — 每个页面应该只使用一次 */
#app { max-width: 1200px; margin: 0 auto; }
/* 通配符 — 全局重置(性能开销大,慎用) */
* { box-sizing: border-box; }
</style>
</head>
<body>
<div id="app">
<p>这是普通段落。</p>
<p class="warning">这是警告内容!</p>
</div>
</body>
</html>最佳实践:ID 选择器特异性很高且不可复用,应优先使用类选择器。通配符
*会遍历所有元素,在大型页面中应避免高频使用。
1.2 组合器(Combinators)
组合器定义了选择器之间的关系。
| 组合器 | 符号 | 语义 | 示例 |
|---|---|---|---|
| 后代组合器 | (空格) |
选中的后代元素(所有层级) | div p |
| 子代组合器 | > |
选中的直接子元素(仅第一层) | div > p |
| 相邻兄弟组合器 | + |
紧跟在第一个元素后的兄弟元素 | h2 + p |
| 后续兄弟组合器 | ~ |
第一个元素之后的所有兄弟元素 | h2 ~ p |
示例代码:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<style>
/* 后代 — 选中 article 内所有 p(任意嵌套层级) */
article p { color: #555; }
/* 子代 — 仅选中 ul 的直接子 li */
ul > li { list-style: disc; }
/* 相邻兄弟 — 选中紧跟在 h2 后面的第一个 p */
h2 + p { font-weight: bold; color: #2a7; }
/* 后续兄弟 — 选中 h2 后面的所有 p */
h2 ~ p { border-left: 3px solid #2a7; padding-left: 1em; }
</style>
</head>
<body>
<article>
<h2>CSS 组合器</h2>
<p>这段文字会同时命中 h2 + p 和 h2 ~ p。</p>
<p>这段只命中 h2 ~ p。</p>
<div>
<p>这段是 article p 的后代匹配。</p>
</div>
<ul>
<li>直接子元素</li>
<li>直接子元素
<ol>
<li>嵌套 li——ul > li 不匹配此项</li>
</ol>
</li>
</ul>
</article>
</body>
</html>注意:组合器可以串联使用,例如
nav ul > li.active + li表示:nav内部的ul的直接子元素li.active之后的第一个li。
1.3 属性选择器
属性选择器允许根据 HTML 属性的存在或值来匹配元素,是 CSS 中最强大的定位工具之一。
语法大全
| 模式 | 含义 |
|---|---|
[attr] |
存在 attr 属性即可 |
[attr="val"] |
属性值 完全等于 val |
[attr^="val"] |
属性值 以 val 开头 |
[attr$="val"] |
属性值 以 val 结尾 |
[attr*="val"] |
属性值 包含 val 子串 |
[attr~="val"] |
属性值是用空格分隔的词列表,其中一个是 val |
[attr|="val"] |
属性值等于 val 或以 val- 开头 |
[attr="val" i] |
加上 i 标志则不区分大小写 |
真实场景示例
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<style>
/* 存在 href 属性的 a 标签 */
a[href] { text-decoration: underline; }
/* 链接到 PDF 文件 */
a[href$=".pdf"]::after {
content: " 📄";
}
/* 外部链接(以 http 开头) */
a[href^="http"] {
color: #d9534f;
}
/* 链接中包含 "example" */
a[href*="example"] {
font-style: italic;
}
/* data-* 自定义属性用法 */
[data-status="active"] {
background: #d4edda;
color: #155724;
}
/* 不区分大小写匹配 — 使用 i 标志 */
[data-type="urgent" i] {
border-left: 4px solid #dc3545;
}
/* 空格分隔的词列表 */
[data-tags~="featured"] {
font-weight: bold;
}
</style>
</head>
<body>
<div data-status="active">当前状态:激活</div>
<div data-type="URGENT">紧急事项(i 标志允许不区分大小写)</div>
<div data-tags="hot featured new">精选内容</div>
<ul>
<li><a href="/page">站内页面</a></li>
<li><a href="https://example.com">示例站</a></li>
<li><a href="https://other.com/doc.pdf">PDF 文档</a></li>
</ul>
</body>
</html>实用技巧:属性选择器与
data-*自定义属性配合使用,可以在不新增类名的情况下实现状态驱动的样式。
1.4 伪类(Pseudo-classes)
伪类以 : 开头,描述元素的特殊状态。它们是 CSS 中最庞大也最强大的选择器家族。
1.4.1 结构性伪类
结构性伪类基于元素在 DOM 树中的位置进行选择。
| 伪类 | 说明 |
|---|---|
:first-child |
父元素的第一个子元素 |
:last-child |
父元素的最后一个子元素 |
:first-of-type |
兄弟元素中该类型的第一个 |
:last-of-type |
兄弟元素中该类型的最后一个 |
:nth-child(n) |
父元素的第 n 个子元素 |
:nth-last-child(n) |
父元素中从后往前第 n 个子元素 |
:nth-of-type(n) |
兄弟元素中该类型的第 n 个 |
:nth-last-of-type(n) |
兄弟元素中该类型的倒数第 n 个 |
:only-child |
父元素唯一的子元素 |
:only-of-type |
兄弟元素中该类型的唯一元素 |
:empty |
没有子元素(包括文本节点)的元素 |
:not(selector) |
不匹配给定选择器的元素 |
:has(selector) |
包含匹配选择器的后代元素的父元素(CSS 选择器第 4 级) |
:nth-child() 参数详解:
参数可以是:
- 数字:
2表示第 2 个 - 关键字:
odd(奇数)、even(偶数) - 公式:
an + b,如2n+1(奇数)、3n(每 3 个一组中的第 3 个)、n+4(从第 4 个开始的所有)
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<style>
/* 斑马条纹表格 */
tbody tr:nth-child(even) {
background: #f2f2f2;
}
/* 第一个元素无需上边框 */
.list-item:first-child {
border-top: none;
}
/* 列表中的第 2 到第 4 项 */
.list-item:nth-child(n+2):nth-child(-n+4) {
background: #fff3cd;
}
/* 每 3 行中第 1 行加粗(3n+1) */
.grid-item:nth-child(3n+1) {
font-weight: bold;
}
/* 空状态提示 — 无子元素的容器 */
.box:empty::after {
content: "(空容器)";
color: #999;
font-style: italic;
}
/* 排除第一个元素 */
li:not(:first-child) {
border-top: 1px solid #ddd;
}
/* :has() — 包含特定子元素的父容器 */
.card:has(> .badge) {
border: 2px solid #ffc107;
}
/* :has() 也支持复杂选择器 */
section:has(h2:target) {
background: #f0f8ff;
}
</style>
</head>
<body>
<table>
<thead>
<tr><th>姓名</th><th>分数</th></tr>
</thead>
<tbody>
<tr><td>张三</td><td>95</td></tr>
<tr><td>李四</td><td>88</td></tr>
<tr><td>王五</td><td>76</td></tr>
<tr><td>赵六</td><td>92</td></tr>
</tbody>
</table>
<ul>
<li>所有非第一项都有上边框</li>
<li>第二项</li>
<li>第三项</li>
</ul>
<div class="card">
<span class="badge">热门</span>
<p>:has() 检测徽章后添加边框</p>
</div>
</body>
</html>1.4.2 用户行为伪类
这些伪类响应用户的交互操作。
| 伪类 | 触发条件 |
|---|---|
:hover |
鼠标悬停(触摸设备需谨慎) |
:active |
元素被激活(如点击按下瞬间) |
:focus |
元素获得焦点(键盘 Tab 或点击) |
:focus-visible |
浏览器认为焦点应当可见(键盘导航最佳) |
:focus-within |
元素自身或其后代获得焦点 |
:target |
元素 ID 匹配 URL 中的片段标识符 |
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<style>
/* 基础交互 */
button {
padding: 8px 20px;
border: 2px solid #007bff;
background: white;
transition: all 0.2s;
}
button:hover {
background: #007bff;
color: white;
}
button:active {
transform: scale(0.96);
}
/* focus vs focus-visible */
.focus-demo:focus {
outline: 2px solid #007bff;
}
.focus-visible-demo:focus-visible {
outline: 3px solid #28a745;
outline-offset: 2px;
}
/* 点击时 focus-visible 不显示轮廓,Tab 键聚焦时显示 */
/* :target — URL 跳转高亮 */
section[id]:target {
background: #fff3cd;
border-left: 4px solid #ffc107;
padding-left: 1em;
}
/* :focus-within — 表单组整体高亮 */
.form-group:focus-within {
border-color: #007bff;
box-shadow: 0 0 0 3px rgba(0,123,255,0.25);
}
</style>
</head>
<body>
<button>悬停并点击我</button>
<p><a href="#section1">跳转到章节 1</a></p>
<!-- 使用 focus-visible 的输入框 -->
<label>
键盘友好输入框(<code>:focus-visible</code>):
<input class="focus-visible-demo" type="text" placeholder="按 Tab 聚焦试试">
</label>
<section id="section1">
<h2>章节 1</h2>
<p>URL 中带有 #section1 时高亮显示。</p>
</section>
<div class="form-group">
<label>邮箱:<input type="email"></label>
</div>
</body>
</html>重要提醒:
focus-visible是focus的现代替代方案——它在鼠标点击时不显示轮廓(避免难看的蓝框),但在键盘 Tab 导航时显示清晰的焦点指示器(无障碍刚需)。这比笼统使用:focus { outline: none }更安全。
1.4.3 表单伪类
专门用于表单控件的状态选择。
| 伪类 | 匹配条件 |
|---|---|
:checked |
被选中的 checkbox 或 radio |
:indeterminate |
不确定状态的 checkbox 或 radio |
:disabled |
被禁用的表单元素 |
:enabled |
可用的表单元素 |
:required |
设置了 required 属性的元素 |
:optional |
未设置 required 属性的元素 |
:valid |
通过约束验证的元素 |
:invalid |
未通过约束验证的元素 |
:in-range |
值在 min/max 范围内的输入 |
:out-of-range |
值超出 min/max 范围的输入 |
:placeholder-shown |
占位文本正在显示的元素 |
:read-only |
只读表单元素 |
:read-write |
可编辑表单元素 |
:default |
一组元素中的默认元素 |
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<style>
/* 选中状态 */
input[type="checkbox"]:checked + label {
color: #28a745;
font-weight: bold;
}
/* 自定义选中指示器 */
input[type="checkbox"]:checked::before {
content: "✓ ";
}
/* 禁用元素样式 */
input:disabled {
background: #e9ecef;
cursor: not-allowed;
}
/* 验证状态 */
input:valid {
border-color: #28a745;
}
input:invalid {
border-color: #dc3545;
}
/* 占位符可见时 */
input:placeholder-shown {
border-color: #ced4da;
}
/* 必填字段标记 */
input:required + .label-text::after {
content: " *";
color: #dc3545;
}
/* 范围验证 */
input[type="number"]:in-range {
background: #d4edda;
}
input[type="number"]:out-of-range {
background: #f8d7da;
}
</style>
</head>
<body>
<form>
<p>
<input type="checkbox" id="agree" checked>
<label for="agree">同意协议</label>
</p>
<p>
<label>邮箱(必填,验证):
<input type="email" required placeholder="请输入邮箱">
<span class="label-text"></span>
</label>
</p>
<p>
<label>年龄(10-100):
<input type="number" min="10" max="100" value="5">
</label>
</p>
<p>
<label>被禁用的输入:
<input type="text" disabled value="不可编辑">
</label>
</p>
</form>
</body>
</html>1.4.4 语言伪类
| 伪类 | 说明 |
|---|---|
:lang(language) |
匹配设置了特定语言的元素(基于 lang 属性) |
:dir(direction) |
匹配特定文字方向的元素(ltr 或 rtl) |
/* 中文内容使用特定字体 */
:lang(zh-CN) {
font-family: "Noto Sans SC", "Microsoft YaHei", sans-serif;
}
/* 阿拉伯语从右到左调整 */
:dir(rtl) {
text-align: right;
}
/* 不同语言引用样式不同 */
blockquote:lang(en) {
border-left: 3px solid #007bff;
}
blockquote:lang(zh-CN) {
border-left: 3px solid #dc3545;
}1.4.5 其他重要伪类
| 伪类 | 说明 |
|---|---|
:root |
文档根元素,相当于 html,但特异性更高 |
:fullscreen |
处于全屏模式的元素 |
:is(selector_list) |
匹配选择器列表中的任意一个(容错:单个失效不影响整体) |
:where(selector_list) |
与 :is() 功能相同,但特异性始终为 0 |
:defined |
匹配已定义的自定义元素 |
:is() 与 :where() 的特异性差异:
/* 传统写法 — 需重复写 section */
section h1, section h2, section h3 { color: #333; }
/* :is() 简化 — 但特异性取列表中最高项 */
:is(section, article, aside) h1 { color: #333; }
/* 等同于 section h1, article h1, aside h1 */
/* 特异性 = 元素选择器(0,0,1) + ID选择器(假设列表中最高) */
/* :where() — 特异性始终为零 */
:where(section, article, aside) h1 { color: #333; }
/* h1 的特异性 = (0,0,1),不受 :where() 内部影响 */
/* 因此任何更具体的选择器都能覆盖它 */<!DOCTYPE html>
<html lang="zh-CN">
<head>
<style>
/* 使用 :root 定义全局 CSS 变量 */
:root {
--primary: #007bff;
--danger: #dc3545;
--spacing: 1rem;
}
/* :is() 简化分组 */
:is(h1, h2, h3, h4).section-title {
border-bottom: 2px solid var(--primary);
}
/* :where() 零特异性,方便覆盖 */
:where(nav, header, footer) a {
text-decoration: none;
}
/* 后续用 .nav-link { text-decoration: underline; } 轻松覆盖 */
</style>
</head>
<body>
<h1 class="section-title">主要标题</h1>
<h2 class="section-title">二级标题</h2>
</body>
</html>1.5 伪元素(Pseudo-elements)
伪元素以 :: 开头,用于创建或选取元素的特定部分。它们是虚拟的 DOM 节点。
| 伪元素 | 说明 |
|---|---|
::before |
在元素内容前插入虚拟子元素(需 content) |
::after |
在元素内容后插入虚拟子元素(需 content) |
::first-letter |
块级元素的第一行第一个字符 |
::first-line |
块级元素的第一行文本 |
::selection |
用户选中的文本 |
::placeholder |
表单输入框的占位文本 |
::marker |
列表项的标记(如 bullet 或数字) |
::backdrop |
全屏元素或对话框后的背景层 |
::file-selector-button |
文件上传控件的按钮部分 |
::cue |
WebVTT 字幕 |
::slotted() |
插槽内容(Shadow DOM) |
实战示例代码:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<style>
/* ::before / ::after — 装饰与内容插入 */
.quote::before {
content: "「";
color: #999;
font-size: 1.5em;
}
.quote::after {
content: "」";
color: #999;
font-size: 1.5em;
}
/* 清除浮动 */
.clearfix::after {
content: "";
display: table;
clear: both;
}
/* ::first-letter — 首字下沉 */
.dropcap::first-letter {
font-size: 3em;
float: left;
line-height: 0.8;
margin-right: 0.1em;
color: #6f42c1;
font-weight: bold;
}
/* ::first-line — 首行样式 */
.intro::first-line {
font-weight: bold;
color: #0056b3;
}
/* ::selection — 自定义选中样式 */
::selection {
background: #b3d4fc;
color: #333;
}
/* ::placeholder — 占位符样式 */
input::placeholder {
color: #aaa;
font-style: italic;
}
/* ::marker — 自定义列表标记 */
ul.custom-list li::marker {
content: "▶ ";
color: #28a745;
font-size: 0.8em;
}
/* 用 ::after 实现 tooltip */
[data-tooltip] {
position: relative;
border-bottom: 1px dashed #999;
cursor: help;
}
[data-tooltip]::after {
content: attr(data-tooltip);
position: absolute;
bottom: 120%;
left: 50%;
transform: translateX(-50%);
background: #333;
color: #fff;
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
white-space: nowrap;
opacity: 0;
transition: opacity 0.2s;
pointer-events: none;
}
[data-tooltip]:hover::after {
opacity: 1;
}
</style>
</head>
<body>
<p class="quote">这是被引号包裹的内容。</p>
<p class="dropcap">首字下沉效果常见于杂志排版,可以大幅提升阅读体验。这段文字用来展示 ::first-letter 的效果。</p>
<p class="intro">这一段的首行将加粗并显示为蓝色。这是通过 ::first-line 伪元素实现的,它只影响块级元素的第一行文本。</p>
<p><input type="text" placeholder="请输入搜索关键词..."></p>
<ul class="custom-list">
<li>自定义标记的项目</li>
<li>另一个项目</li>
</ul>
<p>
<span data-tooltip="这是提示内容">悬停此处</span> 查看 tooltip 效果。
</p>
</body>
</html>关键规则:
::before和::after生成的元素默认是内联元素,且必须设置content属性(可以是空字符串"")。它们在 DOM 中不可见,只存在于 CSS 渲染树中。
1.6 选择器组合使用示例
实际开发中,选择器很少单独使用。让我们看一些真实场景的组合示例。
场景 1:表单验证视觉反馈
/* 必填且无效的输入 */
input:required:invalid {
border: 2px solid #dc3545;
background: #fff5f5;
}
/* 必填且有效,且非空(非 placeholder 状态) */
input:required:valid:not(:placeholder-shown) {
border: 2px solid #28a745;
background: #f0fff4;
}场景 2:卡片组件中的各种状态
/* 被聚焦或包含 .active 子元素的卡片 */
.card:focus-within,
.card:has(.active) {
box-shadow: 0 0 0 3px rgba(0,123,255,0.3);
}
/* 列表中的最后一项且不是唯一一项 */
li:last-child:not(:only-child) {
border-bottom: none;
}
/* 第 3 个之后且不是最后一个 */
li:nth-child(n+4):not(:last-child) {
opacity: 0.8;
}场景 3:响应式导航菜单
/* 当前页面的导航项 — 通过 body 类控制 */
body.page-home .nav-home,
body.page-about .nav-about {
font-weight: bold;
border-bottom: 2px solid #007bff;
}
/* 鼠标悬停或焦点时,并且不是当前页面 */
.nav-item:hover:not(.nav-active),
.nav-item:focus-visible:not(.nav-active) {
color: #007bff;
}二、层叠(Cascade)
"层叠"(Cascade)是 CSS 名称的由来,也是 CSS 最核心的冲突解决机制。当多个规则同时应用于同一个元素时,层叠决定了哪个规则最终生效。
2.1 层叠优先级总排序
CSS 层叠按以下递增优先级排序(后面的覆盖前面的):
浏览器默认样式
↓
用户声明样式(用户浏览器设置)
↓
作者普通声明样式(开发者写的 CSS)
↓
CSS 动画(@keyframes)
↓
作者 !important 声明
↓
用户 !important 声明
↓
过渡(transition)但请注意: 实际排序规则更加精细,现代 CSS 的完整层叠排序(从低到高)为:
- Normal 层:浏览器样式 → 用户样式 → 作者样式
- Animation 层
- Important 层:作者 !important → 用户 !important
- Transition 层
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<style>
/* 作者普通声明 */
p { color: blue; }
/* 动画中的颜色 — 比普通声明优先级高 */
@keyframes color-shift {
from { color: red; }
to { color: green; }
}
p.animated {
animation: color-shift 2s infinite;
}
/* !important — 最高优先级之一 */
p { color: black !important; }
/* 但 transition 比 !important 更高 */
</style>
</head>
<body>
<!-- 浏览器默认: 黑色(已被覆盖) -->
<!-- 作者普通: 蓝色(已被动画覆盖) -->
<!-- 动画: 红/绿(已被 !important 覆盖) -->
<!-- !important: 黑色(生效) -->
<p>这是黑色!</p>
<p class="animated">这也是黑色(!important 胜出)</p>
</body>
</html>2.2 来源与重要性权重
CSS 声明按来源(Origin)和重要性(!important 标志)组合成一个 3×2 矩阵:
| 普通 | 重要 (!important) | |
|---|---|---|
| 浏览器样式 | 最低 | 第 4 低 |
| 用户样式 | 第 3 低 | 第 2 高 |
| 作者样式 | 第 2 低 | 最高(不含过渡) |
也就是说,作者 !important > 用户 !important > 浏览器 !important > 作者普通 > 用户普通 > 浏览器普通。
2.3 特异性(Specificity)计算
当多个来源相同、重要性相同的声明冲突时,浏览器会比较特异性。特异性用四组数值表示(通常写作 (a, b, c, d)):
| 级别 | 对应选择器类型 | 计数 |
|---|---|---|
| a | 内联样式 (style="") |
1 或 0 |
| b | ID 选择器的数量 | 计数 |
| c | 类、伪类、属性选择器的数量 | 计数 |
| d | 元素、伪元素选择器的数量 | 计数 |
比较规则:从左到右逐位比较,数值大的胜出。(1,0,0,0) > (0,15,0,0)。
计算示例
/* 选择器 (a,b,c,d) */
* {} /* (0,0,0,0) */
li {} /* (0,0,0,1) */
li:first-line {} /* (0,0,0,2) — 元素 + 伪元素 */
ul li {} /* (0,0,0,2) */
ul ol+li {} /* (0,0,0,3) */
h1 + *[rel=up] {} /* (0,0,1,1) — 属性选择器 = 类级别 */
ul ol li.red {} /* (0,0,1,3) */
li.red.level {} /* (0,0,2,1) */
#x34y {} /* (0,1,0,0) */
#s12:not(FOO) {} /* (0,1,0,1) — :not() 本身不计,内部选择器计入 */
.foo:is(.bar, #baz) {} /* (0,1,1,0) — :is() 取内部最高特异性 */重要:
:not()、:is()、:has()的特异性取参数列表中最高的选择器的特异性。而:where()的特异性始终为(0,0,0,0)。
完整对比示例
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<style>
/* (0,0,0,1) */
div { color: red; }
/* (0,0,1,0) */
.highlight { color: blue; }
/* (0,0,1,1) */
div.highlight { color: green; }
/* (0,1,0,0) */
#special { color: purple; }
/* (0,1,1,0) */
#special.highlight { color: orange; }
/* (1,0,0,0) — 内联样式 高于所有这些 */
</style>
</head>
<body>
<!-- 内联样式特异性 (1,0,0,0),所以这里显示 black -->
<div id="special" class="highlight" style="color: black;">
这段文字是什么颜色?
</div>
<!-- 答案是 black(内联 > 任何选择器!) -->
</body>
</html>2.4 位置规则(源顺序)
当特异性完全相同时,后定义的样式覆盖先定义的。
/* 特异性都是 (0,0,1,0) */
.btn { background: blue; } /* 先定义 */
.btn { background: red; } /* 后定义 → 生效 */
/* 重要:外部样式表中后面的链接也会覆盖前面的 */
/* link1.css: .btn { color: blue; } */
/* link2.css: .btn { color: red; } → 生效 */
/* 相同选择器,后出现的优先 */
h1 { color: blue; }
h1 { color: red; } /* 生效 */2.5 @layer 级联层
CSS 级联层(@layer)是 CSS 新增的强大功能,允许作者显式控制层的优先级,无需依靠选择器特异性或源顺序。
/* 定义层的优先级顺序:越后面的层优先级越高 */
@layer base, components, utilities;
/* 在层内定义样式 */
@layer base {
a { color: blue; } /* (0,0,0,1) */
}
@layer components {
.nav-link { color: darkblue; } /* (0,0,1,0) */
}
@layer utilities {
.link-reset { color: inherit; } /* (0,0,1,0) */
}
/* 未放入任何层的样式优先级最高("未分层"层)*/
a { color: black; } /* 最终生效 */无层叠层的样式优先级排序:
@layer A (最低)
↓
@layer B
↓
@layer C
↓
未分层样式 (最高)实际应用:
/* 框架/重置层 — 最低优先级 */
@layer reset {
* { margin: 0; padding: 0; box-sizing: border-box; }
}
/* 组件层 — 中等优先级 */
@layer components {
.card {
background: white;
border: 1px solid #ddd;
border-radius: 8px;
}
}
/* 工具层 — 较高优先级 */
@layer utilities {
.bg-primary { background: #007bff !important; }
}
/* 覆盖层 — 最高优先级(未分层) */
.emergency-banner {
background: #dc3545 !important;
}@layer 的优势:即使
@layer reset中的选择器特异性比@layer components高,由于层的顺序,components 仍然会覆盖 reset。这大大简化了大型项目中的样式管理。
2.6 组件级 CSS 策略
结合层叠规则,现代组件级 CSS 常用以下策略:
策略 1:BEM + 特异性控制
/* 所有组件使用相同的特异性级别 */
.card { }
.card__title { }
.card__content { }
/* 不同的组件间用层叠层控制顺序 */策略 2:CSS Modules / Scoped CSS
/* 框架自动添加独特属性选择器提升特异性 */
/* .Card_title_abc123 { color: blue; } */策略 3:utility-first(Tailwind)
/* 所有工具类特异性相同,通过层叠层控制 */
.layers-base .text-center { text-align: center; }
/* 用 @layer 控制优先级顺序 */三、继承(Inheritance)
继承是 CSS 中属性值从父元素自动传递到子元素的机制。理解继承特性对于编写简洁、可维护的样式至关重要。
3.1 继承属性 vs 非继承属性
常见的继承属性:
| 类别 | 属性 |
|---|---|
| 字体相关 | font-family, font-size, font-weight, font-style, font-variant, font |
| 文本相关 | color, text-align, text-indent, line-height, letter-spacing, word-spacing, text-transform, direction |
| 可见性 | visibility |
| 列表 | list-style, list-style-type, list-style-position |
| 光标 | cursor |
| 其他 | orphans, widows, quotes |
常见的非继承属性:
| 类别 | 属性 |
|---|---|
| 盒模型 | width, height, margin, padding, border |
| 布局 | display, position, top, right, bottom, left, float, clear, overflow |
| 背景 | background, background-color 等 |
| 定位 | z-index, transform |
| 其他 | box-shadow, text-shadow, outline, opacity(文本不继承但容器影响) |
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<style>
.parent {
color: #333; /* 继承 */
font-family: Georgia, serif; /* 继承 */
font-size: 18px; /* 继承 */
border: 2px solid red; /* 不继承 */
padding: 20px; /* 不继承 */
background: #f0f0f0; /* 不继承 */
text-align: center; /* 继承 */
}
.child {
/* 没有覆盖 font-family、color、font-size */
/* 所以它们从 .parent 继承 */
border: 1px solid blue; /* 显式设置 */
}
</style>
</head>
<body>
<div class="parent">
<p>父元素:有红色边框和灰色背景。</p>
<div class="child">
子元素:字体和颜色从父元素继承,<br>
但蓝色边框是自身设置的,没有红色边框和灰色背景。
</div>
</div>
</body>
</html>3.2 显式继承与控制关键字
CSS 提供了四个通用关键字来控制属性值的行为:
| 关键字 | 效果 |
|---|---|
inherit |
强制继承父元素的计算值(即使是非继承属性) |
initial |
使用属性的初始值(CSS 规范定义的默认值,非浏览器默认) |
unset |
继承属性 → 表现如 inherit;非继承属性 → 表现如 initial |
revert |
恢复为浏览器默认样式(重置层叠中更低的来源) |
revert-layer |
恢复为上一级层叠层的值 |
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<style>
.parent {
color: darkblue;
border: 2px solid darkblue;
font-size: 20px;
}
.child-inherit {
border: inherit; /* 强制继承父元素的 border */
color: inherit; /* 显式继承 color(其实已经是继承的) */
}
.child-initial {
color: initial; /* color 的初始值是 black(不同浏览器可能不同) */
font-size: initial; /* font-size 的初始值是 medium(通常是 16px) */
}
.child-unset {
color: unset; /* 继承属性 → 实际效果同 inherit,继承父元素的值 */
border: unset; /* 非继承属性 → 实际效果同 initial,无边框 */
}
.child-revert {
color: revert; /* 恢复为浏览器默认(通常是黑色) */
}
</style>
</head>
<body>
<div class="parent">
<p class="child-inherit">inherit:边框和颜色与父元素相同。</p>
<p class="child-initial">initial:颜色为 initial(黑色),字体大小为 medium。</p>
<p class="child-unset">unset:继承颜色,边框恢复为初始值(无边框)。</p>
<p class="child-revert">revert:颜色恢复为浏览器默认样式。</p>
</div>
</body>
</html>3.3 all: unset 重置所有属性
all 属性允许一次性重置元素的所有属性。
/* 完全重置元素的样式 — 相当于元素"裸奔" */
.reset-all {
all: unset;
}
/* 强制继承所有属性 */
.inherit-all {
all: inherit;
}
/* 恢复所有属性到初始值 */
.initial-all {
all: initial;
}
/* 恢复所有属性到浏览器默认 */
.revert-all {
all: revert;
}实际应用场景:重置第三方组件或统一规范化:
/* 重置按钮为裸元素(常用于 CSS 框架的 reset) */
.button-reset {
all: unset;
display: inline-flex; /* 重新设置必要的属性 */
cursor: pointer;
}
/* 在 Shadow DOM 中重置所有宿主样式 */
:host {
all: initial;
}警告:
all: unset会重置display等属性,通常需要紧接着重新设置display值。all: revert在重置浏览器默认样式后还能保留作者样式表中的普通声明。
四、@规则(At-rules)
@规则是 CSS 中的指令语句,以 @ 开头,用于控制 CSS 的行为、引入外部资源、定义条件规则等。
4.1 @import — 引入外部样式表
/* 在样式表顶部引入其他 CSS 文件 */
@import url('reset.css');
@import url('theme.css') screen and (min-width: 768px);
@import url('print.css') print;
/* 支持媒体查询条件 */
@import 'mobile.css' (max-width: 480px);注意:
@import必须位于样式表的最顶部(在任何其他规则之前),否则会被忽略。出于性能考虑,在 HTML 中使用<link>替代@import通常是更好的选择(避免串行下载)。
4.2 @media — 媒体查询
@media 是响应式设计的核心,允许根据设备特性应用条件样式。
/* 基础语法 */
@media media-type and (condition) {
/* 规则 */
}
/* 常用媒体类型:screen, print, speech, all */
/* 断点示例 */
/* 移动端优先 */
.container {
width: 100%;
padding: 0 16px;
}
/* 平板(≥768px) */
@media (min-width: 768px) {
.container {
max-width: 720px;
margin: 0 auto;
}
}
/* 桌面(≥1024px) */
@media (min-width: 1024px) {
.container {
max-width: 960px;
}
}
/* 大屏(≥1280px) */
@media (min-width: 1280px) {
.container {
max-width: 1200px;
}
}
/* 复杂条件组合 */
/* 横屏且宽度大于 768px */
@media screen and (min-width: 768px) and (orientation: landscape) {
.sidebar { display: block; }
}
/* 不支持悬停的设备(触摸屏) */
@media (hover: none) {
.dropdown:hover .dropdown-menu { display: none; }
.dropdown:active .dropdown-menu { display: block; }
}
/* 偏好减少动画 */
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
}
}
/* 深色模式 */
@media (prefers-color-scheme: dark) {
:root {
--bg: #1a1a2e;
--text: #eee;
--primary: #6c63ff;
}
}
/* 打印样式 */
@media print {
nav, footer, .sidebar { display: none; }
body { font-size: 12pt; color: #000; }
a::after { content: " (" attr(href) ")"; }
}4.3 @supports — 特性检测
@supports 允许根据浏览器是否支持特定 CSS 属性/值来决定是否应用样式,是渐进增强的利器。
/* 检测是否支持 grid 布局 */
@supports (display: grid) {
.layout {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 20px;
}
}
/* 不支持时回退 */
.layout {
display: flex;
flex-wrap: wrap;
}
.layout > * {
width: 30%;
margin: 1.5%;
}
/* 逻辑运算符 */
/* and */
@supports (display: grid) and (gap: 20px) {
/* 两者都支持 */
}
/* or */
@supports (display: grid) or (display: -ms-grid) {
/* 至少支持一个 */
}
/* not */
@supports not (selector(:has(*))) {
/* 浏览器不支持 :has() 时的回退 */
.card { border-left: 3px solid #007bff; }
}
/* 实际应用:检测较新的 CSS 功能 */
@supports (container-type: inline-size) {
.component { container-type: inline-size; }
}
@supports (color-mix(in srgb, red, blue)) {
.theme { background: color-mix(in srgb, var(--primary) 80%, black); }
}4.4 @font-face — 自定义字体
@font-face {
font-family: 'CustomFont';
src: url('fonts/custom.woff2') format('woff2'),
url('fonts/custom.woff') format('woff');
font-weight: 400;
font-style: normal;
font-display: swap; /* 控制加载期间的行为 */
}
@font-face {
font-family: 'CustomFont';
src: url('fonts/custom-bold.woff2') format('woff2'),
url('fonts/custom-bold.woff') format('woff');
font-weight: 700;
font-style: normal;
font-display: swap;
}
/* 使用 */
body {
font-family: 'CustomFont', 'Noto Sans SC', sans-serif;
}
/* font-display 可选值 */
/* auto — 浏览器默认行为 */
/* block — 最多 3 秒不可见,然后降级字体,字体加载完成后切换 */
/* swap — 立即显示降级字体,字体加载完成后切换(最常用) */
/* fallback — 100ms 不可见 + 3 秒降级字体,超时后不再切换 */
/* optional — 100ms 不可见 + 可选降级,让浏览器决定是否使用 */4.5 @keyframes — 动画关键帧
@keyframes slide-in {
/* 起始状态 */
from {
transform: translateX(-100%);
opacity: 0;
}
/* 结束状态 */
to {
transform: translateX(0);
opacity: 1;
}
}
@keyframes bounce {
0% { transform: translateY(0); }
50% { transform: translateY(-30px); }
80% { transform: translateY(-10px); }
100% { transform: translateY(0); }
}
@keyframes pulse {
0%, 100% {
transform: scale(1);
opacity: 1;
}
50% {
transform: scale(1.05);
opacity: 0.8;
}
}
/* 使用动画 */
.element {
animation: slide-in 0.5s ease-out forwards;
}
.pulse-btn {
animation: pulse 2s ease-in-out infinite;
}
/* 动画缩写属性:name duration timing-function delay iteration-count direction fill-mode play-state */
.bounce-element {
animation: bounce 0.6s cubic-bezier(0.68, -0.55, 0.27, 1.55) 0s infinite alternate;
}五、CSS 命名方法论简介
当项目规模增大,良好的 CSS 组织方式变得至关重要。以下是两种主流方法论。
5.1 BEM(Block__Element--Modifier)
BEM 是 Block Element Modifier 的缩写,是一种通过命名约定来保持选择器特异性一致、避免冲突的 CSS 方法论。
| 组件 | 含义 | 示例 |
|---|---|---|
| Block | 独立、可复用的组件 | .card |
| Element | Block 的组成部分,依赖 Block 存在 | .card__title |
| Modifier | Block 或 Element 的变体状态 | .card--featured, .card__title--large |
完整示例:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<style>
/* Block */
.card {
background: #fff;
border: 1px solid #e0e0e0;
border-radius: 8px;
padding: 16px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
/* Element */
.card__image {
width: 100%;
border-radius: 4px;
}
.card__title {
font-size: 1.25rem;
font-weight: 600;
margin: 12px 0 8px;
}
.card__content {
color: #555;
line-height: 1.6;
}
.card__actions {
margin-top: 16px;
display: flex;
gap: 8px;
}
/* Modifier */
.card--featured {
border-color: #007bff;
box-shadow: 0 4px 12px rgba(0,123,255,0.2);
}
.card--compact {
padding: 8px;
font-size: 0.875rem;
}
.card__title--large {
font-size: 1.5rem;
}
.card__actions--centered {
justify-content: center;
}
</style>
</head>
<body>
<!-- 普通卡片 -->
<div class="card">
<h3 class="card__title">普通卡片</h3>
<p class="card__content">这是普通卡片的内容。</p>
<div class="card__actions">
<button>操作 1</button>
<button>操作 2</button>
</div>
</div>
<!-- 特色卡片(大标题 + 居中按钮) -->
<div class="card card--featured">
<h3 class="card__title card__title--large">推荐卡片</h3>
<p class="card__content">特色卡片使用 modifier 改变了外观。</p>
<div class="card__actions card__actions--centered">
<button>立即购买</button>
</div>
</div>
</body>
</html>BEM 优势:
- 所有选择器特异性相同(单层类选择器,
(0,0,1,0)) - 命名即文档——从类名就能看出组件结构
- 不会出现嵌套选择器导致的特异性竞争
注意(非官方但常见的实践):
/* Block + Element + Modifier 组合 */
.card__title--large { }
/* SCSS 中的 BEM 嵌套写法(输出保持扁平) */
.card {
&__title {
&--large { }
}
&--featured { }
}5.2 工具类 CSS(Utility-First)
以 Tailwind CSS 为代表的工具类方法,使用细粒度、单一职责的工具类直接在 HTML 中组合样式。
Tailwind 的核心哲学:
<!-- 传统 CSS -->
<div class="card">
<h3 class="card__title">标题</h3>
<p class="card__content">内容</p>
</div>
<!-- Utility-First 方式 -->
<div class="bg-white border border-gray-200 rounded-lg p-4 shadow-md">
<h3 class="text-lg font-semibold my-3">标题</h3>
<p class="text-gray-600 leading-relaxed">内容</p>
</div>Tailwind 优势:
| 优势 | 说明 |
|---|---|
| 无命名负担 | 不再需要为组件起名 |
| 一致性 | 所有间距/颜色/字体都来自预设的设计系统 |
| 小体积 | 最终只打包使用到的类(PurgeCSS) |
| 响应式内置 | md:flex, lg:text-lg 等前缀 |
| 状态变体 | hover:bg-blue-600, focus:ring-2 |
<!-- 响应式卡片示例(使用 Tailwind 类名) -->
<div class="max-w-sm mx-auto bg-white rounded-xl shadow-md overflow-hidden md:max-w-2xl">
<div class="md:flex">
<div class="md:shrink-0">
<img class="h-48 w-full object-cover md:h-full md:w-48"
src="image.jpg" alt="图片描述">
</div>
<div class="p-8">
<div class="uppercase tracking-wide text-sm text-indigo-500 font-semibold">
分类标签
</div>
<a href="#" class="block mt-1 text-lg leading-tight font-medium text-black hover:underline">
文章标题
</a>
<p class="mt-2 text-gray-500">文章简介内容……</p>
</div>
</div>
</div>BEM vs Utility-First 对比:
| 维度 | BEM | Utility-First |
|---|---|---|
| 选择器特异性 | 始终 (0,0,1,0) | 始终 (0,0,1,0) |
| HTML 长度 | 短,类名少 | 长,类名多 |
| 学习曲线 | 低 | 中(需记忆类名) |
| 组件抽取时机 | 一开始就定义 | 出现重复时提取 |
| 修改样式风险 | 需改 CSS 文件 | 限制在设计系统内 |
| 团队一致性 | 依赖命名规范 | 强一致性(受框架约束) |
总结
CSS 选择器、层叠与继承是 CSS 引擎工作的三大基石。理解它们不仅帮助你写出正确的样式,更能让你以 "浏览器的方式" 思考 CSS,从而:
-
选择器 — 精确地定位元素,利用特异性规则写出可预测、可维护的样式。善用
:is()和:where()控制特异性,用伪类和伪元素减少 JavaScript 依赖。 -
层叠 — 优先级是 CSS 冲突解决的核心。理解来源、重要性、特异性、位置和
@layer的五层递进关系,从根本上避免 "加 !important" 的恶性循环。 -
继承 — 利用继承属性减少重复代码,使用
inherit、initial、unset和revert精确控制属性值传递。 -
@规则 —
@media、@supports、@font-face、@keyframes和@layer扩展了 CSS 的能力边界,使其从静态样式表进化为动态的、条件式的、可编程的样式语言。 -
命名方法论 — BEM 和 Utility-First 解决了工程化问题,让团队在大型项目中保持一致的样式策略。
CSS 看似简单,实则深邃。当你真正理解选择器的精度、层叠的神奇和继承的优雅时,你将不再是 "写 CSS",而是 "设计 CSS 系统"。
本文发布于 2024-08-26,涵盖 CSS 选择器 Level 4 规范中的大多数特性。部分较新的特性(如 :has()、@layer、@container)已在主流现代浏览器中广泛支持,建议在生产环境前查阅 Can I Use 确认兼容性。