上一章结尾,你写出了人生第一个"结构规范"的新闻页。但它长这样:黑字白底、宋体字号、所有东西从上往下堆------能看,但不好看。
问题出在:HTML 只负责"有什么",不负责"好不好看"。
这一章,我们开始给页面穿上衣服。
但别急着学"怎么变红变蓝"。CSS 真正难的地方,从来不是"怎么写",而是**"为什么我写了却不生效"** 。几乎每个前端新手都经历过这样的时刻:明明写了 color: red,字就是不红。
这一章要解决的正是这件事。学完本章,你能:
- 知道样式该写在哪里(以及写在哪会影响优先级);
- 熟练用选择器精确点中你想改的元素;
- 最重要的是------当两条规则打架时,能一眼说出谁赢。
最后这条,才是 CSS 名字里那个 C (Cascading,层叠)的真正含义。
8.1 CSS 是什么:把一条规则拆开看
CSS = Cascading Style Sheets(层叠样式表)。 三个词,一个一个拆:
| 词 | 含义 |
|---|---|
| Style Sheets(样式表) | 它是一张"外观清单":谁该是什么颜色、多大字号、摆在哪里 |
| Cascading(层叠) | 当多条规则都指向同一个元素时,浏览器按一套固定算法决定谁生效 |
一句话概括两者的分工:
HTML 描述"有什么",CSS 描述"长什么样"。 两者分开写,是前端工程化的第一块基石------改外观不动结构,改结构不动外观。
现在看一条 CSS 规则长什么样:
css
/* 选择器:选谁 */ p {
color: red; /* 声明一:属性 + 冒号 + 值 + 分号 */
font-size: 18px; /* 声明二 */
} /* 整个大括号叫"声明块" */
拆成四个组成部分:
| 名称 | 上面的例子 | 作用 |
|---|---|---|
| 选择器(selector) | p |
决定"给谁加样式" |
| 声明块(declaration block) | { ... } |
装着一到多条声明 |
| 声明(declaration) | color: red; |
一条具体的样式 |
| 属性 / 值 | color / red |
改哪一项 / 改成什么 |
这四个小地方,新手几乎都会踩一次:
css
/* ❌ 错:冒号写成了等号 */
p { color = red; }
/* ❌ 错:值和单位之间加了空格 */
p { font-size: 18 px; }
/* ❌ 错:最后一条声明漏了分号(下一条会被吞掉) */
p {
color: red
font-size: 18px; /* 这一条直接失效 */
}
/* ✅ 对 */
p {
color: red;
font-size: 18px;
}
⚠️ 注意:CSS 报错不弹窗。 JavaScript 出错会在控制台看到红色的报错,但 CSS 写错了,浏览器只会安静地"忽略这一条" ,页面照常显示,只是样式没生效。这就是为什么初学者常常"找不到自己错在哪"------没有报错,就是最难的报错 。 排查方法:按
F12打开 DevTools,在 Elements → Styles 面板里看。写错的声明会被划掉或标黄,一眼就能发现。
8.2 样式写在哪里:三种引入方式
CSS 代码可以放在三个地方。它们不只是"位置不同",优先级也不同------这是本章第一个重点。
方式一:行内样式 ------直接写在标签的 style 属性里。
html
<p style="color: red; font-size: 18px;">这段文字是红色的。</p>
方式二:内部样式表 ------写在 <head> 里的 <style> 标签中。
html
<head>
<style>
p { color: red; }
.title { font-size: 24px; }
</style>
</head>
方式三:外部样式表 ------单独一个 .css 文件,用 <link> 引入。
html
<head>
<link rel="stylesheet" href="style.css">
</head>
css
p { color: red; }
.title { font-size: 24px; }
💡 提示:
<link>的两个属性别写错。rel="stylesheet"是固定的(告诉浏览器"这是个样式表"),href指向文件路径。顺序不能反,名字不能拼错,否则样式完全不加载 ,而且控制台不会报错------这是新手最常见的"样式全丢了"的原因。
三种方式怎么选?一张表看明白:
| 引入方式 | 作用范围 | 复用性 | 适用场景 |
|---|---|---|---|
| 行内样式 | 单个标签 | ✘ 完全不能复用 | 基本不用。只在 JS 动态改单个元素、或调试时临时试 |
| 内部样式表 | 当前页面 | ▲ 仅本页可用 | 单页小 demo、学习时快速实验 |
| 外部样式表 | 可被多个页面引入 | ✔✔ 一个文件全站共用 | 实际项目的标准做法 |
优先级的真相(这里很多教程都写错了)
你一定听过这个说法:
"行内样式 > 内部样式 > 外部样式。"
这个说法有一半是错的。 真实规则是:
⚠️ 纠偏:内部样式和外部样式"权重相同",谁后加载谁赢。 浏览器把它们一视同仁 ,都叫"作者样式表"(author stylesheet)。当两条规则权重相同时,后出现的那条生效 (这叫"源顺序")。 所以:
<style>写在<link>后面 ,内部样式赢;写在<link>前面 ,外部样式赢。它们之间没有天生的高低。
完整、准确的优先级顺序是这样的:
| 顺序(从低到高) | 说明 |
|---|---|
| 1. 外部样式表 / 内部样式表 | 同一档 。权重相同时,看谁在文档里后出现 |
2. 行内样式(style="...") |
普通声明里它最高------因为它离元素最近 |
3. 样式表里带 !important 的声明 |
能盖过普通的行内样式 |
4. 行内样式里带 !important 的声明 |
作者样式的最高级别 |
🔍 一个容易被忽略的细节:
!important能打败行内样式。 很多老教程说"行内样式优先级最高",这是不严谨的。准确说法是:行内样式在"普通声明"里最高;但样式表里任何一条带!important的规则,都能盖过它。 不过请记住------!important是急救药,不是保健品。它强行改变优先级,会让后面的样式更难预测。8.6 节我们细说它的代价。
场景选择建议:
| 场景 | 推荐 | 原因 |
|---|---|---|
| 多页面网站 | 外部样式表 | 一个文件全站共用,改一处全站生效,还能被浏览器缓存 |
| 单页小练习 | 内部样式表 | 一个文件搞定,不用管路径 |
| 用 JS 动态改某个元素 | 行内样式(el.style) |
只影响这一个元素,立即生效 |
| 想快速试一个效果 | 行内(然后搬进样式表) | 试完记得搬走,别留在正式代码里 |
8.3 基础选择器:怎么"点中"目标
样式写好了,接下来是核心问题:怎么选中你想改的那些元素? 这就是选择器的活儿。
先看最常用的五个:
| 选择器 | 写法 | 例子 | 命中什么 | 优先级权重 |
|---|---|---|---|---|
| 标签(元素) | 标签名 | p { } |
页面上所有 <p> |
低 |
| 类 | . + 类名 |
.card { } |
所有 class="card" 的元素 |
中 |
| ID | # + id |
#app { } |
唯一的 id="app" 的元素 |
高 |
| 通配 | * |
* { } |
所有元素 | 0 |
| 属性 | [属性] |
[disabled] { } |
带该属性的元素 | 中 |
css
/* 标签选择器:所有段落 */
p { line-height: 1.8; }
/* 类选择器:所有带 class="card" 的元素(最常用) */
.card { border: 1px solid #ddd; border-radius: 8px; }
/* ID 选择器:id 是唯一的,一页只能有一个 */
#app { max-width: 900px; }
/* 通配选择器:所有元素(通常只用来做全局重置) */
* { box-sizing: border-box; }
/* 属性选择器:带 type="text" 的输入框 */
input[type="text"] { border: 1px solid #ccc; }
HTML 里怎么配合?注意 class 和 id 的写法差别:
html
<p class="card">我是卡片</p>
<div id="app">页面根容器</div>
<h2 class="card big">可以同时用多个类</h2>
🔍 为什么实际项目里"类选择器"用得最多? 三个原因:
- 可复用 ------一个
.card能套在无数个元素上,样式写一次到处用;- 语义清晰 ------
.card一看就知道是"卡片",比div有意义;- 优先级适中 ------不高不低,好覆盖,不会动不动就要
!important去打架。这条经验非常值钱:
id是给 JavaScript 用的"挂钩",不是给 CSS 用的选择器 。给元素加id是为了让 JS 能找到它;拿id去写样式,会让优先级飙到最高,后面对付它很痛苦(8.6 节会看到为什么)。
8.4 关系选择器:根据"位置"来选
除了按"身份"选,CSS 还能按元素之间的位置关系来选。这是选择器里最能体现"级联"威力的部分。
html
<nav>
<a href="/">首页</a> <!-- nav 的直接子元素 -->
<span>
<a href="/a">子元素里的链接</a> <!-- nav 的后代,但不是直接子元素 -->
</span>
</nav>
<h1>标题</h1>
<p>第一段</p>
<p>第二段</p>
| 选择器 | 写法 | 含义 | 例子 |
|---|---|---|---|
| 后代 | A B(空格) |
A 里面所有层级的 B | nav a → nav 里任何位置的 a(两个都命中) |
| 子代 | A > B |
A 的直接子元素里的 B | nav > a → 只有首页那个 a(子元素里的不算) |
| 相邻兄弟 | A + B |
紧跟在 A 后面的那一个 B | h1 + p → 只有"第一段" |
| 通用兄弟 | A ~ B |
A 后面所有同级的 B | h1 ~ p → 第一段、第二段都命中 |
| 并集 | A, B |
满足 A 或 B | h1, h2 { } → h1 和 h2 都改 |
css
/* 后代:导航里所有的链接 */
nav a { color: #333; text-decoration: none; }
/* 子代:只改 nav 的直接子级链接(更精确,不误伤) */
nav > a { font-weight: bold; }
/* 相邻兄弟:紧跟标题后的第一段,去掉上边距 */
h1 + p { margin-top: 0; }
/* 通用兄弟:标题之后的所有段落 */
h1 ~ p { color: #555; }
/* 并集:层级标题共用一套字号 */
h1, h2, h3 { font-family: "Microsoft YaHei", sans-serif; }
💡 提示:
>和空格差在哪?nav a(空格)会命中 nav 内部所有层级 的<a>,哪怕它藏在nav > span > div > a里;nav > a(大于号)只命中最外一层的直接子元素 。 什么时候用>?当你担心"样式误伤到嵌套更深的元素"时 。比如导航栏里的下拉菜单里也有链接,你可能只想改最外层那一排------这时>就是保险。
⚠️ 注意:+和~只认"后面的同级兄弟"。A + B里的 B 必须是 A 之后 的、同一个父元素下 的兄弟。写在 A 前面的兄弟不算,不是同级的(比如 A 的子元素)也不算。这两个选择器日常用得少,但读别人代码时会遇到,得认得。
8.5 伪类与伪元素:选中"状态"和"看不见的部分"
有两类元素,普通选择器永远选不到:
- 同一个元素的不同状态------鼠标悬停时的按钮、获得焦点的输入框;
- 元素身上不存在的"部件"------段落的第一行、元素前方那个装饰小箭头。
它们分别靠伪类 和伪元素来选。
伪类(pseudo-class) :选中元素的某个状态 ,单冒号 : 开头。
| 伪类 | 含义 |
|---|---|
:hover |
鼠标悬停在上面时 |
:focus |
获得焦点时(输入框被点中、按钮被 Tab 选中) |
:first-child |
是父元素的第一个子元素时 |
:last-child |
是父元素的最后一个子元素时 |
:nth-child(n) |
是父元素的第 n 个子元素时(可以做斑马纹) |
:not(选择器) |
排除符合某条件的元素 |
伪元素(pseudo-element) :选中元素身上一个虚拟的"部分" ,双冒号 :: 开头。
| 伪元素 | 含义 |
|---|---|
::before |
在元素内容之前 插入一块(需配 content) |
::after |
在元素内容之后 插入一块(需配 content) |
::first-line |
元素的第一行文字 |
::placeholder |
输入框的占位提示文字 |
::marker |
列表项前面的那个圆点/数字 |
css
/* 鼠标悬停时变色(最常见的交互效果) */
button:hover { background: #2563eb; color: #fff; }
/* 输入框获得焦点时描边 */
input:focus { outline: 2px solid #2563eb; }
/* 列表斑马纹:奇数行加底色 */
li:nth-child(odd) { background: #f7f7f7; }
/* 排除最后一个元素(常用来去掉多余的间距) */
li:not(:last-child) { margin-bottom: 8px; }
/* 伪元素:在标题前加一个装饰小竖条 */
.title::before {
content: ""; /* 必须有,哪怕空字符串 */
display: inline-block;
width: 4px; height: 16px;
background: #2563eb;
margin-right: 8px;
}
/* 伪元素:让段落第一行的字稍大一点(杂志排版常见) */
p::first-line { font-weight: bold; }
⚠️ 注意:伪元素不写
content就完全不会出现。::before和::after必须至少有一条content声明(哪怕是content: ""),否则浏览器认为"没有内容可插入",整个伪元素都不渲染 。这是新手调装饰性伪元素时最常卡住的地方------样式全写对了,就是看不见,因为漏了content。
📌 记忆点:单冒号 vs 双冒号。
- 伪类 :单冒号
:hover(因为它是"真的元素 + 一个状态",元素本身是存在的);- 伪元素 :双冒号
::before(因为它选的是"虚拟出来的一块",本来不存在)。也就是说:"冒号前有东西"的用单冒号,是伪类;"凭空造出一块"的用双冒号,是伪元素。 (历史上老的写法也用单冒号写伪元素,比如
:before,浏览器为兼容还认;但新代码请一律用双冒号。)
8.6 优先级:两条规则打架,谁说了算
现在到本章最关键的一节。
假设下面两条规则都指向同一个 <p>,字到底什么颜色?
css
#intro { color: blue; }
p { color: red; }
答案是 蓝色 。为什么?因为 ID 选择器的优先级高于标签选择器。
浏览器判断优先级,用的是三列对比法 ------注意,它不是一个加法:
| 列 | 名称 | 里面装什么 |
|---|---|---|
| A | ID 列 | 有多少个 #id |
| B | 类列 | 有多少个 .class、属性选择器、伪类 |
| C | 元素列 | 有多少个标签名、伪元素 |
比较规则:从 A 列开始,谁大谁赢。A 列相同才比 B 列,B 列相同才比 C 列。三列全一样,才看"源顺序"(后写的赢)。
⚠️ 纠偏:优先级不是 100 / 10 / 1 的十进制加法。 很多教程教你"ID 算 100 分、类算 10 分、标签算 1 分,加起来比大小"。这个算分法是错的 ------它会把
.a.b.c.d.e.f.g.h.i.j.k(11 个类)算成 110 分,得出"能打败 1 个 ID(100 分)"的结论。 真相是:三列是独立的,列与列之间不会"进位"。 11 个类在 B 列是 11,但在 A 列是 0;而 ID 在 A 列是 1。A 列一比,11 个类直接输。 请记住这句话:再多的类,也打不过一个 ID。
用例子把规则做实:
| 选择器 | A(ID) | B(类/属性/伪类) | C(元素/伪元素) | 谁赢 |
|---|---|---|---|---|
p |
0 | 0 | 1 | --- |
.card |
0 | 1 | 0 | 比 p 强(B 列先赢) |
p.card |
0 | 1 | 1 | 比 .card 强(C 列胜出) |
#app p |
1 | 0 | 1 | 比 p.card 强(A 列直接定胜负) |
#app |
1 | 0 | 0 | 比 #app p 弱(A 平,比 C) |
几个权重为零的特例,一定要记牢:
| 选择器 | 权重 | 说明 |
|---|---|---|
*(通配) |
0 | 不贡献任何权重 |
组合符 > + ~ 空格 |
不贡献 | 它们只是"关系",本身不加权重 |
:where(...) |
0 | 整个东西权重为 0(8.8 节细说) |
关于 !important 的忠告
!important 能无视上面所有规则,强行让一条声明获胜:
css
p { color: red !important; } /* 这条会盖过任何普通声明 */
它能救急,但代价很大:
- 它破坏了"优先级"这套可预测的体系 ------别人看代码时,会以为某条规则生效,结果被一个
!important悄悄改掉了; - 它会引发"军备竞赛" ------你用
!important盖过一条,别人只好用更强的!important盖你,代码越来越乱; - 一旦满屏
!important,样式就彻底失控了。
✅ 正确的思路:遇到"改不动样式",先别掏
!important。 先按这个顺序排查:
- 我选中的真的是那个元素吗?(DevTools 里看看元素有没有被选中)
- 我的选择器权重是不是太低?(换个更高权重的,或调整选择器写法)
- 是不是有更靠后的同权重规则把它盖了?(调整源顺序)
- 真的都排查过了,才考虑
!important,并且写好注释说明原因。
8.7 层叠与继承:为什么有些样式会"自己传下去"
CSS 里有个神奇的现象:你只给 <body> 设了字体,为什么里面所有文字都跟着变了?
这叫继承(inheritance) ------某些属性会自动从父元素传给子元素。但不是所有属性都继承,这条界线要记清楚:
| 会继承的属性(典型) | 不会继承的属性(典型) |
|---|---|
color 文字颜色 |
border 边框 |
font-* 字体相关(字号、字重、字族) |
margin / padding 内外边距 |
line-height 行高 |
width / height 宽高 |
text-align 文本对齐 |
background 背景 |
letter-spacing 字间距 |
display 显示方式 |
规律其实很好记:
💡 想通一件事:继承的是"文字类的属性",不继承的是"盒子类的属性"。 文字相关(颜色、字体、行高)传给子孙很合理------一篇文章本来就该统一文字风格;而边框、边距、宽高不该 继承,否则给
<div>加个边框,里面所有元素都长出边框,那就灾难了。
如果某个不继承的属性,你就是想让它继承 ,用关键字 inherit 强制:
css
.child { border: inherit; } /* 强行继承父元素的边框 */
.title { color: initial; } /* 恢复成属性默认值(本来的初始值) */
.box { color: unset; } /* 该继承的就继承,不该继承的就用初始值 */
同时,别忘了"层叠"的另一半------源顺序。 当权重完全相同时,写在后面的赢:
css
p { color: red; }
p { color: blue; } /* 两条同权重,后者生效 → 文字是蓝色 */
⚠️ 一个真实的坑:
<style>和<link>的顺序。 前面说过,内部样式和外部样式权重一样 。所以如果你把<style>写在<link>之后 ,内部样式会盖掉外部样式;反过来,外部样式会盖掉内部样式。顺序不同,结果不同 ------这也是"我明明改了 CSS 文件,页面却没变"的一种常见原因(另一种更隐蔽:浏览器缓存了旧的.css文件,按Ctrl+F5强制刷新即可)。
8.8 现代选择器(入门彩蛋,认识即可)
CSS 这些年一直在进化。有几个新选择器已经在所有主流浏览器里稳定可用,虽然本章是入门,但值得你先混个脸熟------以后看到不会懵。
| 选择器 | 作用 | 例子 |
|---|---|---|
:is(A, B) |
简写"或",继承参数里最高的权重 | .post :is(h1, h2, h3) a { } |
:where(A, B) |
同 :is(),但权重恒为 0 |
:where(.prose) ul { } |
:has(选择器) |
父级选择器 :元素含有某内容时命中 | .card:has(img) { } |
css
/* :is() ------ 少写一长串重复的选择器 */
/* 原来:.post h1 a, .post h2 a, .post h3 a { ... } */
.post :is(h1, h2, h3) a { color: inherit; }
/* :where() ------ 权重为 0,专用来写"随时可以被覆盖"的基础样式 */
:where(h1, h2, h3) { margin: 0; } /* 谁都能轻松盖掉它 */
/* :has() ------ "父选择器",过去只能用 JS 实现的场景 */
.card:has(img) { display: grid; } /* 卡片里有图片时,换个布局 */
.field:has(input:invalid) { border-color: red; } /* 输入非法时,给整个字段描红 */
🔍
:has()为什么被称为"游戏规则改变者"? 因为它让 CSS 第一次能够**"向上去看"**------根据"里面有没有某个东西",来决定"外面那个元素"的样式。过去这只能靠 JavaScript 监听事件、手动加减 class 类名来做。现在,一个选择器搞定。 它的支持已经覆盖了 Chrome、Edge、Safari、Firefox 的当前版本,可以放心用。
另外还有两个趋势值得知道(点到为止):
- 原生 CSS 嵌套 :以前只有 Sass 这类预处理器才有"在父规则里写子规则"的能力,现在浏览器原生支持 了------
.card { h2 { ... } &:hover { ... } }直接就是有效的 CSS。 @layer(级联层) :给样式分层,层的顺序优先于选择器权重。它是大型项目治理"优先级战争"的终极手段------不过这是进阶话题,本书后面讲到工程化时再展开。
💡 入门阶段不必急着用这些。 先把基础选择器和优先级吃透------它们是理解一切新技术的底座。
!important和@layer解决的都是"优先级失控"的问题,而最好的优先级管理,是根本不让它失控。
8.9 动手:给新闻页穿上第一件衣服
现在把前面所有知识用起来。还记得第 7 章那个"结构规范但很素"的新闻页吗?我们给它加上样式。
第一步:建一个外部样式表(这是正式项目的做法,别再写行内样式了)。
目录结构:
text
前端练习/
├── news.html
└── css/
└── style.css
第二步:在 HTML 里引入样式表。
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>科技日报 · AI 助手走进课堂</title>
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<header class="site-header">
<h1 class="logo">科技日报</h1>
<nav class="site-nav">
<a href="/">首页</a>
<a href="/tech">科技</a>
<a href="/edu">教育</a>
</nav>
</header>
<main class="container">
<article class="post">
<h2 class="post-title">AI 助手走进课堂</h2>
<p class="post-meta">2026-10-08 · 记者 张明</p>
<p>近日,多所学校开始试点 AI 教学助手......</p>
<p>据参与试点的教师反馈,学生对这种新形式的接受度很高。</p>
</article>
<aside class="sidebar">
<h3>相关推荐</h3>
<ul>
<li><a href="/a">AI 如何改变备课方式</a></li>
<li><a href="/b">教育数字化的三年</a></li>
</ul>
</aside>
</main>
<footer class="site-footer">© 2026 科技日报</footer>
</body>
</html>
第三步:写样式表。
css
/* ===== 全局重置与基础 ===== */
* { box-sizing: border-box; }
body {
margin: 0;
font-family: "Microsoft YaHei", system-ui, sans-serif;
line-height: 1.8;
color: #333;
background: #f7f8fa;
}
a { color: #2563eb; text-decoration: none; }
a:hover { text-decoration: underline; } /* 伪类:悬停时加下划线 */
/* ===== 页头 ===== */
.site-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px 24px;
background: #fff;
border-bottom: 1px solid #e5e7eb;
}
.logo { margin: 0; font-size: 20px; }
.site-nav a {
margin-left: 16px;
}
.site-nav a:not(:last-child)::after { /* 伪元素 + 伪类 + 关系选择器 */
content: "|";
color: #ccc;
margin-left: 16px;
}
/* ===== 内容区 ===== */
.container {
max-width: 900px;
margin: 24px auto;
padding: 0 24px;
display: grid;
grid-template-columns: 2fr 1fr; /* 主内容 + 侧栏 */
gap: 24px;
}
.post {
background: #fff;
padding: 24px;
border-radius: 8px;
}
.post-title { margin-top: 0; font-size: 28px; }
.post-meta { color: #888; font-size: 14px; }
/* 文章正文:紧跟标题的第一段去掉上边距 */
.post h2 + p { margin-top: 0; }
/* 侧栏 */
.sidebar {
background: #fff;
padding: 24px;
border-radius: 8px;
}
.sidebar h3 { margin-top: 0; }
.sidebar ul { padding-left: 18px; }
.sidebar li:not(:last-child) { margin-bottom: 8px; }
/* ===== 页脚 ===== */
.site-footer {
text-align: center;
padding: 24px;
color: #888;
font-size: 14px;
}
第四步:打开看效果,并用 DevTools 验证。
把浏览器窗口缩放到不同宽度,再用 F12 检查:Elements → Styles 面板里,你能看到每一条样式是"从哪个选择器来的",以及哪条被划掉了 (被更高优先级的规则覆盖了)。学会看这个面板,你的 CSS 调试能力会直接翻倍。
💡 本节的样式表用到了什么?
*(通配)·.class(类)· 标签 ·:hover/:not()/:last-child(伪类)·::after(伪元素)· 后代 / 子代 / 相邻兄弟(关系)· 还有下一章才细讲的display: flex/grid。 这张表里的每一行,都是本章讲过的选择器。 你不是在抄代码------你是在验证自己真的会用了。
8.10 本章小结
- CSS = 层叠样式表,负责"长什么样";一条规则 = 选择器 + 声明块,声明 = 属性 + 值。
- 三种引入方式 :行内(
style)· 内部(<style>)· 外部(<link href>)。正式项目一律用外部样式表。 - 优先级真相 :外部与内部权重相同 ,同级看源顺序;行内样式在普通声明里最高,但能被样式表里的
!important盖过。 - 选择器 :基础(标签 / 类 / ID / 通配 / 属性)→ 关系(空格 /
>/+/~/,)→ 伪类(:hover等状态)→ 伪元素(::before等虚拟部分)。 - 优先级不是十进制加法 ,是三列
(ID, 类, 元素)从左到右比较------再多的类也打不过一个 ID。 - 通配
*和:where()权重为 0;组合符不贡献权重。 - 继承的是"文字类"属性 (颜色、字体、行高),不继承的是"盒子类"属性(边距、边框、宽高)。
!important是急救药------先排查选择器和权重,别急着用它。
8.11 练习题
第 1 题(辨析) 下面这个说法对吗?如果不对,错在哪?请改正。
text
"外部样式表的优先级比内部样式表低,
所以内部样式里的规则总能盖过外部样式里的规则。"
参考答案
说法错误。
错在哪 :内部样式表和外部样式表权重完全相同 ,浏览器把它们都当作"作者样式表"处理,没有天生的高低之分。
正确的规则 :当两条规则权重相同时 ,看源顺序 ------谁在文档里后出现,谁生效。
所以:
<head> 里的写法 |
结果 |
|---|---|
<style> 写在 <link> 之前 |
外部样式生效(它后出现) |
<style> 写在 <link> 之后 |
内部样式生效(它后出现) |
也就是说:决定胜负的不是"内部还是外部",而是"谁都位置更靠后"。
补充 :真正比它俩高一档的,是行内样式 (style="...")------它在普通声明里优先级最高。但注意:样式表里带 !important 的规则,仍然能盖过普通行内样式。
第 2 题(计算) 下面这些选择器,各自的三列权重分别是多少?把它们从低到高排序。
text
① p
② .card
③ #app .card
④ article.post h2
⑤ *
⑥ a:hover
参考答案
按 (A: ID, B: 类/属性/伪类, C: 元素/伪元素) 数:
| 选择器 | A(ID) | B(类/伪类) | C(元素) |
|---|---|---|---|
① p |
0 | 0 | 1 |
② .card |
0 | 1 | 0 |
③ #app .card |
1 | 1 | 0 |
④ article.post h2 |
0 | 1 | 2 |
⑤ * |
0 | 0 | 0 |
⑥ a:hover |
0 | 1 | 1 |
从低到高排序 :⑤ *(全 0)< ① p(0,0,1)< ② .card(0,1,0)< ⑥ a:hover(0,1,1)< ④ article.post h2(0,1,2)< ③ #app .card(1,1,0)
几个要点:
- ⑤
*通配符权重为 0 ,但它确实会命中所有元素------"权重为 0"和"不生效"是两回事。 - ②
.card和 ④article.post h2都是 B 列 = 1,但 ④ 的 A 列相同(都是 0)、比到 C 列(2 > 0)才输赢------说明 A 列没分出胜负时,才轮到后面比。 - ③ 只要 A 列有 1 个 ID,B 列和 C 列无论多少都会被压住------这就是"现在的类打不过 ID"的含义。
- ⑥
a:hover::hover是伪类,算在 B 列,所以是 (0,1,1) 而不是 (0,0,2)。
第 3 题(纠错) 小美想让下面这段代码里所有段落的字变红,但一直没成功。请找出至少 4 处错误。
html
<!DOCTYPE html>
<html>
<head>
<style>
p { color = red }
.text { font-size: 16 px; }
</style>
</head>
<body>
<p class="text">第一段
<p class="text">第二段
</body>
</html>
参考答案
四处错误:
| # | 错误 | 正确写法 | 说明 |
|---|---|---|---|
| ① | color = red |
color: red; |
CSS 的"属性"和"值"之间用冒号,不是等号 |
| ② | 第一条声明结尾漏分号 | color: red; |
虽然它在大括号最后一行,分号仍建议写------否则一旦往下加声明就会连带出错 |
| ③ | 16 px |
16px |
数值和单位之间不能有空格 |
| ④ | <p> 标签没有闭合 |
<p class="text">第一段</p> |
HTML 的 <p> 虽然能"自动闭合",但这是坏习惯,容易造成结构错乱 |
逐条解释:
- ① 和 ③ 属于"写错了但页面不报错" ------浏览器只会静默忽略 这一条声明,所以你会觉得"我明明写了却没效果"。这就是 8.1 节强调的:CSS 错误不弹窗,只能靠 DevTools 排查。
- ④ 是 HTML 的问题 。虽然浏览器对
<p>有"自动闭合"的容错,但别依赖它 。规范的写法是每段都写</p>。
修复后的正确代码:
html
<!DOCTYPE html>
<html>
<head>
<style>
p { color: red; }
.text { font-size: 16px; }
</style>
</head>
<body>
<p class="text">第一段</p>
<p class="text">第二段</p>
</body>
</html>
第 4 题(实战) 按要求写出选择器(只写选择器,不用写声明块内容)。
html
<ul class="list">
<li>项目一</li>
<li>项目二</li>
<li>项目三</li>
</ul>
<p>段落一</p>
<p>段落二</p>
text
① 选中所有 li
② 只选中"项目二"
③ 选中所有 li,但除了最后一个
④ 鼠标悬停在 li 上时
⑤ 选中 p 里除第一段以外的所有段落
参考答案
| # | 需求 | 选择器 | 说明 |
|---|---|---|---|
| ① | 所有 li | .list li |
用后代选择器 限定在 .list 里,比单写 li 更精确 |
| ② | 只要"项目二" | .list li:nth-child(2) |
:nth-child(2) 选第 2 个子元素 |
| ③ | li 除去最后一个 | .list li:not(:last-child) |
:not() 排除 :last-child |
| ④ | li 悬停时 | .list li:hover |
:hover 伪类 |
| ⑤ | p 除第一段外 | p:not(:first-child) |
排除是"第一个子元素"的 p |
几个要点:
- ② 的
:nth-child(2)是"第 2 个孩子" ,注意它数的是"在父元素里的名次",不是"第 2 个 li" 。如果<ul>里第一个元素不是<li>,结果会不一样------这是:nth-child最容易误解的地方。 - ③ 和 ⑤ 都用
:not()。:not()里可以放选择器,用来"排除"。⑤ 的p:not(:first-child)表示"p 元素里,不是第一个子元素的那些",也就是"段落二"。 - ① 写成
.list li而不是直接li------因为页面上可能还有别的列表,加一层.list能避免样式误伤(这就是"关系选择器"的价值)。
第 5 题(判断) 下面四段描述,判断对错并说明理由。
text
① 通配选择器 * 权重为 0,所以它能被任何样式覆盖。
② 只要用了 !important,样式就一定能生效。
③ ::before 和 ::after 不写 content 属性也能显示。
④ 给 body 设置 font-size 后,里面所有文字都会继承这个字号。
参考答案
| # | 判断 | 理由 |
|---|---|---|
| ① | ⚠️ 基本对,但表述要修正 | * 权重确实是 0 ,同等条件下 会被任何有权的选择器盖过。但"能覆盖"不等于"一定被覆盖"------如果没有别的规则 指向该元素,* 的样式照样生效 |
| ② | ⛔ 错 | !important 只在作者样式表内部 争胜负。如果 CSS 写错了、选择器根本没命中元素、或者有语法错误,!important 一样救不了。它只解决"优先级打架",不解决"没选中" |
| ③ | ⛔ 错 | ::before / ::after 必须有 content 属性 (哪怕 content: ""),否则完全不渲染。这是最常见的踩坑点 |
| ④ | ✅ 对 | font-size 属于"文字类属性",会继承 。所以在 body 上设一次,全站文字都会跟着改变------这正是"全局设字号"这个常用技巧的原理 |
深入说明:
- ①的细节 :
*权重为 0,意味着任何 一个标签选择器(权重 0,0,1)都能盖过它。所以*通常只用来做"全局重置",比如* { box-sizing: border-box; }------反正它权重最低,不会干扰别处。 - ②的教训 :
!important是"优先级层面"的武器。如果问题出在更底层 (选择器没写对、元素根本不存在、文件没加载),它完全无能为力 。这就是本书反复强调"先排查、后!important"的原因。 - ④的后半句 :要注意继承的是字号 ,但如果子元素自己写了
font-size,就会覆盖继承值(继承的优先级最低------任何直接命中的规则都比"继承来的"优先)。
下一章预告:给页面"上色"。 有了选择器这把钥匙,下一步就是往里面填具体的样式了。第 9 章「颜色、文本与背景」会讲:颜色有哪几种写法(关键字 / HEX / RGB / HSL)各自适合什么场合、字体怎么设才不乱、文字的行高与对齐怎么做才舒服、背景色和背景图怎么配------让页面从"能看"变成"好看"的第一步。
说一声「继续」,我们就往下走。