CSS 基础
这一课你将能做到
- 使用选择器(元素、类、ID、属性、后代、组合器)精确选中页面元素
- 理解层叠、继承和特异性的计算规则,能预测样式冲突时的最终结果
- 使用 CSS 自定义属性(变量)管理颜色、间距等设计令牌,写出可维护的样式代码
为什么要学
HTML 搭好了骨架,CSS 负责让页面「好看」。但 CSS 不是简单的「键值对装饰语言」——它的核心机制是层叠(Cascading),即当多个规则同时作用于一个元素时,浏览器如何决定哪个生效。不理解层叠、继承和特异性,你会在「为什么我的样式不生效」「为什么改了这里那里也变了」的问题中反复挣扎。
CodeForge 的课程卡片列表有统一的品牌色、间距和圆角,这些值在几十个地方重复出现。使用 CSS 自定义属性(--primary: #2563eb)可以把这些值集中管理,改一处全局生效,避免「漏改一个地方导致颜色不一致」的尴尬。
先建立图像
浏览器如何决定元素最终样式?
1. 来源:浏览器默认样式 < 用户样式 < 作者样式(你的 CSS)< !important
2. 层叠:后写的覆盖先写的(同优先级、同特异性时)
3. 特异性:行内 > ID > 类/属性/伪类 > 元素/伪元素
4. 继承:部分属性(如 color、font-family)自动继承父元素
部分属性(如 margin、padding)不继承
跟着做
步骤 1:选择器类型与用法
创建 css-intro.html:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CSS 基础练习</title>
<style>
/* 元素选择器 */
p { line-height: 1.6; }
/* 类选择器 */
.card { background: white; border-radius: 12px; padding: 1.5rem; }
/* ID 选择器 */
#hero { text-align: center; padding: 3rem 1rem; }
/* 后代选择器:nav 里面的所有 a */
nav a { color: #334155; text-decoration: none; }
/* 子代选择器:ul 的直接子元素 li */
ul > li { margin-bottom: 0.5rem; }
/* 相邻兄弟选择器:紧跟 h2 后面的第一个 p */
h2 + p { color: #64748b; font-size: 0.9375rem; }
/* 通用兄弟选择器:h2 后面的所有 p */
h2 ~ p { margin-top: 0.5rem; }
/* 属性选择器 */
input[type="email"] { border: 1px solid #cbd5e1; }
a[href^="https"] { color: #2563eb; } /* 以 https 开头 */
a[href$=".pdf"] { color: #dc2626; } /* 以 .pdf 结尾 */
/* 伪类 */
a:hover { text-decoration: underline; }
button:focus-visible { outline: 2px solid #2563eb; outline-offset: 2px; }
li:first-child { font-weight: 600; }
/* 伪元素 */
h1::after {
content: "";
display: block;
width: 60px;
height: 4px;
background: #2563eb;
margin-top: 0.75rem;
border-radius: 2px;
}
</style>
</head>
<body>
<nav>
<ul>
<li><a href="/">首页</a></li>
<li><a href="/courses">课程</a></li>
<li><a href="https://example.com">外部链接</a></li>
</ul>
</nav>
<section id="hero">
<h1>CSS 基础精讲</h1>
<p>掌握选择器、层叠与现代样式管理。</p>
</section>
<section class="card">
<h2>课程特色</h2>
<p>实战驱动,每课都有可运行代码。</p>
<p>覆盖 Flexbox、Grid、响应式设计。</p>
</section>
<form>
<input type="email" placeholder="邮箱">
<input type="password" placeholder="密码">
</form>
</body>
</html>
保存并打开,观察不同选择器的效果。特别留意 h2 + p 只选中紧跟在 h2 后面的第一个 p,而 h2 ~ p 选中 h2 后面所有同级的 p。
步骤 2:理解特异性(Specificity)
<style>
/* 特异性:0-1-0(一个类) */
.title { color: blue; }
/* 特异性:0-2-0(两个类) */
.card .title { color: green; }
/* 特异性:1-0-0(一个 ID) */
#main-title { color: red; }
/* 特异性:1-1-0(一个 ID + 一个类),最高 */
#main-title.title { color: purple; }
</style>
<h1 id="main-title" class="title">这段文字是什么颜色?</h1>
答案是紫色。特异性计算采用 (a, b, c) 三段式:
a= 行内样式数(style="...")b= ID 选择器数c= 类选择器 + 属性选择器 + 伪类数- 最后比较元素/伪元素数(通常不写出)
比较时从 a 开始,大的胜出;a 相同再比 b,以此类推。
步骤 3:层叠与继承
<style>
body {
color: #334155; /* 被继承 */
font-family: system-ui, sans-serif; /* 被继承 */
margin: 0; /* 不被继承 */
}
.highlight {
color: #2563eb; /* 覆盖继承的 color */
}
</style>
<body>
<p>这段文字继承 body 的颜色。</p>
<p class="highlight">这段文字是蓝色。</p>
</body>
color、font-family、line-height、text-align 等属性默认继承。margin、padding、border、background、width 等默认不继承。可用 inherit 强制继承,或用 initial 重置为默认值。
步骤 4:使用 CSS 自定义属性
<style>
:root {
--primary: #2563eb;
--primary-hover: #1d4ed8;
--text-main: #0f172a;
--text-muted: #64748b;
--surface: #ffffff;
--radius: 12px;
--space-sm: 0.5rem;
--space-md: 1rem;
--space-lg: 2rem;
}
@media (prefers-color-scheme: dark) {
:root {
--text-main: #f8fafc;
--surface: #1e293b;
}
}
.btn {
background: var(--primary);
color: white;
padding: var(--space-sm) var(--space-md);
border-radius: var(--radius);
}
.btn:hover {
background: var(--primary-hover);
}
</style>
自定义属性(CSS Variables)在 :root(等价于 <html>)中定义时是全局的,在任意选择器中定义时是局部作用域。配合 @media (prefers-color-scheme: dark) 可以自动适配系统深色模式。
讲清楚要点
-
选择器的精确度与性能:ID 选择器在 CSS 中特异性很高,但因其不可复用,实际项目中应优先使用类选择器。过于复杂的选择器(如
div.container > ul.nav li.item a span)不仅难以维护,还会增加浏览器匹配计算量。 -
特异性不是分数,是元组:
(0, 1, 0)不是「10分」,(0, 0, 10)也不是「10分」。(0, 1, 0)永远大于(0, 0, 99),因为 ID 的权重层级高于类。不要试图用「增加类名数量」来击败 ID。 -
!important是最后手段:它的特异性高于一切普通规则,但会破坏正常的层叠逻辑,导致后续维护极其困难。仅在覆盖第三方库内联样式或极少数硬需求时使用。 -
继承减少重复声明:如果整个页面使用相同字体和文字颜色,在
body或:root上设置一次即可,不用每个元素都写。需要覆盖时,在特定选择器中重写。 -
自定义属性让主题切换变得简单:修改
:root中的--primary值,所有引用它的按钮、链接、边框色会同步更新。这比全局搜索替换颜色值安全得多,也支持运行时通过 JavaScript 修改(document.documentElement.style.setProperty('--primary', 'red'))。 -
:focus-visible替代:focus::focus会在鼠标点击后也显示焦点轮廓,视觉上不美观,导致很多开发者粗暴地outline: none,严重损害键盘可访问性。:focus-visible只在键盘聚焦时显示轮廓,是现代最佳实践。
你可能会懵的地方
-
「类选择器和 ID 选择器哪个好?」 优先用类选择器。ID 在页面中只能出现一次,无法复用样式;且 ID 特异性过高,后续很难覆盖。ID 的主要用途是页内锚点(
<a href="#section">)和 JavaScript 操作(getElementById)。 -
「为什么后写的 CSS 会覆盖先写的?」 这是层叠(Cascading)的规则之一:当来源和特异性相同时,后声明的规则胜出。这也是为什么 CSS 文件通常按「基础 → 组件 → 工具类」的顺序引入,确保后面的能覆盖前面的。
-
「
* { margin: 0; padding: 0; }这种重置好吗?」 过于粗暴。*选择器会匹配所有元素,包括你没想到的(如dialog、input),且特异性为 0,容易被意外覆盖。现代推荐用更精确的重置,如*, *::before, *::after { box-sizing: border-box; }配合特定元素的 margin/padding 重置。 -
「CSS 变量和预处理器变量(Sass/Less)有什么区别?」 CSS 自定义属性是原生支持的,运行时可用 JS 读写,支持层叠和作用域,浏览器能实时计算。Sass 变量在编译时替换为静态值,运行时无法修改。两者可以共存:Sass 管理编译期常量,CSS 变量管理运行时主题。
-
「
rem、em、px到底怎么选?」px是绝对单位,适合边框(1px)。rem相对于根元素(<html>)的字体大小,适合间距和尺寸,配合根元素字体大小调整可实现整体缩放。em相对于父元素的字体大小,适合组件内部局部缩放(如按钮的内边距随按钮字号变化)。
错法对照
| 错法 | 问题 | 正解 |
|---|---|---|
| 滥用 ID 选择器写样式 | 无法复用,特异性过高,难以覆盖 | 用类选择器定义样式,ID 留给 JS 和锚点 |
用 !important 解决所有冲突 | 破坏层叠,后续维护噩梦 | 先检查选择器特异性,通过增加特异性或调整顺序解决 |
给所有元素写 margin: 0 | 破坏表单、列表等默认样式 | 使用现代 CSS Reset(如 Normalize.css 或自己写针对性重置) |
| 选择器嵌套过深 | 性能差,耦合度高 | 保持扁平,优先使用类选择器 |
| 颜色值硬编码在几十处 | 改品牌色时漏改、错改 | 使用 CSS 自定义属性集中管理 |
用 :focus { outline: none; } | 键盘用户无法看到焦点 | 用 :focus-visible 替代,或提供自定义焦点样式 |
卡点急救
-
样式写了但不生效:按
F12→ Elements → 选中元素 → Styles 面板,看目标规则是否被划掉。如果被划掉,看覆盖它的规则在哪里(特异性更高或更靠后)。也可以点击「Computed」标签看最终计算值。 -
修改一个颜色,其他地方也变了:检查是否用了元素选择器(如
p { color: red; })影响到了不相关的段落。解决方案:给需要特殊样式的元素加类名,用类选择器限定范围。 -
深色模式下文字颜色不对:检查是否使用了硬编码的
color: #000而没有适配prefers-color-scheme。把所有颜色提取到 CSS 自定义属性中,在@media (prefers-color-scheme: dark)中覆盖对应变量。
面试常问
-
「CSS 选择器的优先级(特异性)如何计算?」 特异性按
(a, b, c)三段元组计算:a为行内样式数量;b为 ID 选择器数量;c为类选择器、属性选择器和伪类数量。比较时从a到c依次比较,大的胜出。例如#app .title的特异性是(0, 1, 1),大于.card .title的(0, 0, 2)。!important优先级最高,但应避免滥用。 -
「CSS 的层叠顺序是怎样的?」 层叠按三个维度决定:一、来源和重要性(浏览器默认 < 用户样式 < 作者样式 <
!important);二、特异性(选择器的权重);三、源代码顺序(同优先级同特异性时,后写的覆盖先写的)。理解这三个维度就能预测任何样式冲突的结果。 -
「CSS 自定义属性有什么优势?如何在运行时修改?」 优势:原生支持无需编译;支持作用域和继承;可通过媒体查询响应环境变化(如深色模式);可在运行时用 JavaScript 动态修改。JS 修改方式:
document.documentElement.style.setProperty('--primary', '#dc2626')。修改后,所有引用该变量的元素会自动更新,无需逐个选择器修改。
自测三问
- 一个元素同时被
#app .title、section h1和h1[style="color:red"]选中,最终颜色由哪个规则决定?为什么? - 为什么生产环境中推荐用类选择器而不是 ID 选择器来定义样式?
- 你想实现一个跟随系统深色模式自动切换的主题,应该使用什么 CSS 技术?
小结一句话
CSS 的核心不是「把文字变红」,而是「精确选中目标元素并理解浏览器如何裁决冲突」;掌握选择器、特异性、继承和自定义属性,才能写出可预测、可维护的现代样式代码。
延伸
- MDN:CSS 选择器
- MDN:层叠与继承
- MDN:CSS 自定义属性
- 下一课:CSS 盒模型——一切元素都是盒子。