第 04 节 · CSS 引入与选择器
第 04 节 · CSS 引入与选择器
📌 版本信息:基于 CSS Level 3/4 现行规范(选择器、层叠、伪类均为稳定特性;2026-09-06 核对) 📚 来源:MDN CSS 第一步 | MDN 选择器参考 | MDN 层叠基础
当查询面板需要自己的样子
第 03 节做完表单后,页面还停留在浏览器默认样式中:按钮是灰底黑字,输入框又矮又普通,整页像一份没排版的原稿。现在想让查询面板变成以后地图应用里的样子:深色导航条、白底卡片、主色按钮,鼠标悬停时按钮给出反馈。
CSS 就是干这件事的。但它和 HTML 不同:HTML 在结构里直接声明“这一块是什么”,CSS 必须先找到页面上的元素,再决定它长什么样。这一节先解决两件事:CSS 怎么进入页面,以及怎么准确地“点名”元素。
这一节讲什么
本节讲 CSS 规则的组成、三种引入方式、高频选择器、层叠冲突的初步规则,以及最常用的几个伪类。学完这一节可以做到:
- 写出完整、合法的 CSS 规则,并说清选择器与声明的分工
- 在正式项目、单文件练习、临时调试三种场景下选对引入方式
- 用元素、类、ID、后代、子、群组、属性选择器准确命中目标
- 根据优先级和书写顺序判断两条冲突样式谁生效
- 用
:hover、:focus、:not()、:nth-child()处理按钮反馈和列表状态
一条 CSS 规则长什么样
CSS 的最小单元是一条规则,由选择器和声明块组成:
selector { /* 选择器:点名要改谁 */
color: red; /* 声明:属性: 值 */
font-size: 16px; /* 分号分隔多条声明 */
}
可以把页面看成毛坯房:HTML 是墙和房间,CSS 是装修。选择器决定“刷哪面墙”,声明决定“刷成什么颜色、刷多厚”。装饰的顺序也很重要,后面层叠那一节会看到,同一面墙被刷两次时,不是每次都后刷的赢。
三种引入方式
<!-- ① 外链式:行业标配,样式放在独立 CSS 文件里 -->
<link rel="stylesheet" href="./style.css" />
<!-- ② 内嵌式:写在 head 的 <style> 里,单文件练习常用 -->
<style>
p { color: #333; }
</style>
<!-- ③ 行内式:直接写在标签上,仅调试或极个别特例用 -->
<p style="color: red; font-weight: bold;">红色加粗</p>
| 方式 | 场景 | 主要代价 |
|---|---|---|
| 外链 | 正式项目一律用它:可缓存、可复用、结构与样式分离 | 多一次文件请求,现代项目几乎可以忽略 |
| 内嵌 | 单文件练习、临时实验 | 页面一多就难以维护 |
| 行内 | 邮件 HTML、临时调试 | 不能复用,优先级又最高,正常开发应避免 |
外链还有一个好处:多个页面共用同一个 style.css 时,浏览器只下载一次。后面项目里的公共样式、主题变量都会走这条路。
高频选择器
/* ① 元素选择器:选中页面上所有该标签,杀伤面大,适合定基调 */
p { line-height: 1.6; }
/* ② 类选择器:最常用。class 可以有多个,样式围绕它组织 */
.card { border: 1px solid #ddd; }
/* ③ ID 选择器:选中唯一 id;一个页面里 id 只能出现一次 */
#main-map { height: 90vh; }
/* ④ 后代选择器:空格表示“任意层级里”,只影响 .card 内部的 p */
.card p { color: #555; }
/* ⑤ 子选择器:> 只选直接子级,孙辈不选 */
.menu > li { padding: 8px; }
/* ⑥ 群组选择器:逗号表示同时选多个 */
h1, h2, h3 { font-weight: 600; }
/* ⑦ 属性选择器:按属性选,表单控件很常用 */
input[type="text"] { border: 1px solid #ccc; }
input[type="radio"] { accent-color: #1677ff; }
/* ⑧ 类叠加:同时拥有两个类才选中 */
/* HTML:<div class="card active">…</div> */
.card.active { border-color: #1677ff; }
类叠加和后代选择器经常一起出现,比如 .card.active 是“同一个元素同时带两个类”,而 .card .active 是“.card 里面的 .active”,两者不要混淆。
✍️ 命名习惯:class 用小写中划线(
main-title、map-container),按职责命名。style1、red2这种“样子名”在换肤时会让人找不到北。
冲突了听谁的:层叠初步
同一条样式被多处声明时,浏览器按两步定胜负:
- 比优先级:ID 选择器 > 类选择器 > 元素选择器。用记分法理解就是
#id100 分、.class10 分、p1 分,分高者胜。 - 同分比先后:后写的覆盖先写的。
/* 下面两条都命中 <p class="intro">,谁赢? */
p { color: black; } /* 1 分 */
.intro { color: blue; } /* 10 分 → 蓝色赢 */
优先级只比较选择器,不比较声明块里的属性顺序。完整算法里还有继承和 !important,第 29 节会全部展开;现在先把“分数直觉”建立起来,写样式时自然就不会随手堆 ID。
伪类:按状态和位置点名
伪类用冒号开头,选中的不是元素本身,而是元素所处的状态:
/* 悬停:鼠标移上去,按钮的必备反馈 */
button:hover { background: #4096ff; }
/* 激活:鼠标按下去的瞬间 */
button:active { transform: scale(0.98); }
/* 获得焦点:输入框被点击或 Tab 到它身上 */
input:focus { outline: 2px solid #1677ff; }
/* 否定:选中没有 .done 类的 li,待办列表里未完成项 */
li:not(.done) { color: #333; }
/* 结构伪类:第 n 个孩子,表格斑马纹神器 */
tr:nth-child(even) { background: #fafafa; }
li:first-child { font-weight: bold; }
:not() 的优先级按括号里的内容计算,:not(.done) 里的 .done 值 10 分。:nth-child(even) 表示偶数位,先数父元素下的所有子元素,再按位置匹配。
易错点
- 把 CSS 注释写成 HTML 注释:CSS 里混入
<!-- -->可能直接让后面的规则失效。CSS 注释是/* */,这是两种语言,不能互相串门。 - 后代选择器选得过宽:
.card p会命中卡片里任意层级的p,包括嵌套很深的内容。只想管直接子级时用>。 - 类叠加写成空格:
.card .active是“里面的”,.card.active才是“自己同时带两个类”。少一个点,语义完全不同。 - 一上来就用 ID 写样式:ID 优先级 100 分,之后任何类选择器都盖不过它,重构成本很高。页面结构用 class 组织,ID 留给锚点和 JS。
- 把
:hover当“点击效果”::hover是悬停,:active才是按下瞬间。移动端没有悬停状态,按钮反馈另有一套方案,到 JS 事件那一节再补。
动手练习:04 选择器打靶
配套文件:02-web-basics/examples/04-选择器练习页.html。页面里埋了 10 个靶子,答案写在文件内已有的 <style> 注释空位里;每写一条保存一次,看浏览器里的变化。
全部完成后的参考答案:
/* 靶 1:类选择器,10 分 */
.target { color: #e03131; }
/* 靶 2:ID 选择器,100 分 */
#special { background: #ffe066; }
/* 靶 3:后代选择器,11 分 */
.list li { color: #1971c2; }
/* 靶 4:子选择器,11 分 */
.list > li { font-weight: 700; }
/* 靶 5:类叠加,20 分 */
.box.active { color: #2f9e44; }
/* 靶 6:属性选择器,11 分 */
input[type="text"] { border: 2px solid #1c7ed6; }
/* 靶 7:悬停伪类,11 分 */
.btn:hover { background: #f08c00; color: #fff; }
/* 靶 8:结构伪类,12 分 */
.list li:nth-child(even) { background: #f1f3f5; }
/* 靶 9:否定伪类,21 分 */
.list li:not(.done) { color: #d48806; }
/* 靶 10:群组选择器,3 分 */
h1, h2, .task { margin-left: 12px; }
练习里值得多停一下的是靶 3、4、9:
- 靶 3 的后代选择器会连第二层嵌套的
li一起选蓝;靶 4 的子选择器只加粗第一层,两层结果可以同时观察。 - 靶 9 的
.list li:not(.done)是 10 + 1 + 10 = 21 分,会覆盖靶 3 的 11 分,所以未完成项变橙,.done项保持蓝色。 - 靶 8 的偶数位是按
.list的直接子元素数,嵌套在第二层的li不算数。
通关标准:
- 10 个靶子全部命中
- 能说出后代选择器和子选择器的区别,以及各自适合什么场景
- 能解释
.card.active和.card .active为什么是两种选择 - 给页面里的按钮加
:hover效果,并说清优先级分数
自测题
- 三种引入方式在正式项目里怎么选?为什么?
.card p和.card > p的区别?<p class="a b">同时命中.a { color: red; }和.b { color: blue; },最终颜色由谁决定?input[type="text"]和input:text哪个写法对?另一种错在哪?li:nth-child(even)选中的是第几个li?
参考答案
- 外链。可缓存、多页面共用一份、样式与结构分离,团队协作时也更容易维护。
.card p选中卡片里所有层级的p;.card > p只选直接子级。- 两个类选择器同为 10 分,后写的
.b生效,最终是蓝色。 input[type="text"]对;input:text不存在,:text不是有效伪类。- 第 2、4、6… 个,也就是偶数位。
下一步
能“选中”元素之后,下一个问题是“选中的盒子到底占多大”。第 05 节从盒模型与布局流讲起,先把宽度、间距、边距这些最容易失控的概念钉死。