读书小组的网页应在窗口变窄、文字放大或内容改变时保持易读。样式设计要回答三个问题:规则选中了谁、哪项声明决定属性、盒子如何分配空间。理解这些关系,才能解释不同条件下的布局。
CSS 提供样式规则。Bootstrap 整理了常用的布局、工具类及组件规则。理解底层 CSS,才能合理组合它们,并找出冲突原因。
规则选取元素并指定属性
定义
CSS 规则与声明
CSS 规则由选择器与声明块组成。选择器匹配元素,每项声明指定一个属性及其值。
.reading-card {
color: #20354d;
line-height: 1.6;
padding: 1rem;
}
.reading-card 匹配 class 列表含 reading-card 的元素。三项声明设置文字颜色、行高、内边距。属性名拼作 color;分号分隔声明,大括号界定声明块。
CSS 可放在由 link 链接的外部样式表、style 元素或元素的 style 属性中。外部样式表便于多页共用;style 适合小型独立示例;行内样式直接作用于单个元素。
选择器可以描述不同关系:
| 选择器 | 匹配对象 |
|---|---|
p | 段落元素 |
.note | 带有 note 类的元素 |
#next-session | 使用该 ID 的元素 |
.reading-card p | 阅读卡片之内的段落后代 |
.reading-card > h2 | 直接隶属阅读卡片的标题 |
.note.highlight | 同时带有两个类的元素 |
a:hover | 处于鼠标悬停状态的链接 |
a:focus-visible | 浏览器判定应显示可见焦点状态的链接 |
li:nth-child(2) | 在父元素内排行第二的列表项 |
p::first-letter | 适用时,段落所呈现内容的首字母部分 |
空格表示后代,.note.highlight 则要求同一元素同时有两个类。逗号组合独立选择器,例如 h1, h2 匹配两种标题。伪类描述状态或关系;伪元素识别呈现内容的一部分。
层叠与继承回答不同问题
层叠决定元素同一属性的多项竞争声明之中,哪项胜出。继承则在元素没有从层叠取得自身值时,为可继承属性提供值。文字颜色及字体族通常会继承;内边距、边框与外边距通常不会。
在本单元使用的普通、未分层作者样式表规则中,先比较选择器的优先级。依次计算 ID、类/属性/伪类,以及元素名称/伪元素的数目,再按此顺序比较;不能把它们当作可互换的分数相加。优先级相同时,较后出现的声明胜出。普通的行内作者样式优先于普通样式表规则。
常见错误
把样式表位置当作优先规则
外部样式表与 style 元素同属作者来源。在上述普通、未分层条件下,放入 style 并不会自动胜出,仍须比较选择器优先级及源码顺序。
!important 会改变层叠的重要性等级,并不意味着它能胜过任何可能的声明。来源、层、重要声明、动画与过渡效果另有排序规则。
例题
打开浏览器前先预测颜色
<section class="reading-card">
<p class="note">带来一个关于文章的问题。</p>
</section>
body { color: #20354d; font-family: system-ui, sans-serif; }
.note { color: navy; }
.reading-card p { color: teal; }
.reading-card .note { color: maroon; }
.note { color: purple; }
四个颜色选择器都匹配该段落,其优先级依次为 (0,1,0)、(0,1,1)、(0,2,0) 和 (0,1,0)。段落呈栗色,因为比较源码顺序之前,(0,2,0) 已经胜出。若只有两条 .note 规则竞争,最后一条会胜过前一条;但它不能胜过优先级更高的选择器。
段落继承 body 的字体族,但颜色由直接匹配它的声明决定。祖先选择器的优先级不会让继承值胜过子元素自身的声明。
Styles 面板显示匹配与落败的声明;Computed 面板显示最终计算值及继承来源,可据此追查样式。
数值描述文字、颜色与尺寸
font-family 提供有先后次序的字体选项。system-ui, sans-serif 使用系统已有的字体,不下载字体文件。font-size、font-weight、line-height 与 text-align 控制文字的不同方面。没有单位的行高,例如 1.6,会随元素自身的字体大小缩放。
本地网页字体通过 @font-face 声明,并在 font-family 中引用;字体文件须存在于指定 URL。预处理器把其他源语言编译成 CSS;原生自定义属性(如 --accent)则直接通过 var(--accent) 读取。
| 数值或单位 | 解释方式 |
|---|---|
16px | 十六个 CSS 像素;不一定等于屏幕的物理像素 |
1rem | 根元素的字体大小 |
1em | 元素的字体大小;用于 font-size 本身时,则以父元素字体大小为基准 |
宽度的 50% | 适用包含块宽度的一定比例 |
50vw | 视口宽度的一半 |
50vh | 视口高度的一半 |
#245a92 | 以十六进制红、绿、蓝颜色通道表示的颜色 |
rgb(36 90 146) | 同一组 RGB 通道数值的函数写法 |
hsl() 提供另一种颜色表示法。百分比的含义取决于设置的属性,应先确认其基准,不应一律当作“屏幕的百分比”。最大及最小尺寸可限制弹性尺寸,而不必让所有元素都采用固定宽度。
盒模型让宽度计算明确
定义
内容、内边距、边框与外边距
内容盒容纳元素内容。内边距位于内容与边框之间,边框包围内边距,外边距则在边框以外。背景通常会绘制到内边距及边框下方;外边距是透明的。
例题
计算卡片的水平尺寸
.reading-card {
box-sizing: content-box;
width: 240px;
padding: 12px;
border: 2px solid #60758b;
margin: 16px;
}
采用 content-box 时,width 描述内容宽度。因此边框盒宽度是 CSS 像素。再计入两侧外边距,水平占用范围就是 CSS 像素。
若只把 box-sizing 改成 border-box,声明的 240px 便包含内边距及边框。内容宽度变成 CSS 像素;连同外边距的水平范围则是 CSS 像素。两种情况下,外边距都在声明宽度之外。
四值简写 padding: 8px 12px 16px 20px 依次表示上、右、下、左。两个值表示垂直方向、再水平方向;一个值则用于四边。margin 采用同一排列次序。
水平计算不能机械地套用到所有垂直情况:普通块盒的垂直外边距可能折叠。内边距、边框及布局间隙有不同的行为。解释空间差异前,应先检查盒模型。
显示方式与文档流决定盒子的参与方式
在普通文档流中,块盒从新一行开始;宽度为自动值时,通常填满可用的行内空间。行内内容则参与文本行的布局。span 这类非替换行内元素,不会像块盒一样接受宽高;行内图像是替换元素,则可以有尺寸。
inline-block 把盒子放在文本行内,同时允许明确宽高。display: flex 让直接子元素成为弹性项目,排列方式由弹性容器控制。改变显示方式,不会改变 HTML 的语义:导航仍可是一份链接列表,同时让项目水平排列。
.reading-nav {
display: flex;
flex-wrap: wrap;
gap: 0.75rem;
list-style: none;
padding: 0;
}
这里列表的直接子元素形成可换行的一行,并保留一致间隙。浮动则有另一种用途:相邻行内内容可以环绕浮动盒。clear 决定后续盒子须移到哪些浮动盒之下;display: flow-root 则可建立包含其浮动内容的容器。
display: none 让元素不再生成参与布局的盒子。visibility: hidden 隐藏盒子,但保留其布局空间。两者影响附近内容的方式不同,应按期望的布局行为选择。
定位需要参照盒
position 值 | 基本行为 |
|---|---|
static | 使用普通定位;偏移值不会让它重新定位 |
relative | 应用偏移,但保留原有布局位置 |
absolute | 脱离普通文档流,在其包含块内定位 |
fixed | 在没有相关变换的简单文档内,以视口作定位基准 |
sticky | 到达指定偏移位置后,在滚动与包含块的限制内移动 |
.session-card { position: relative; padding: 1rem; }
.session-label { position: absolute; top: 0.5rem; right: 0.5rem; }
本例中,相对定位的卡片建立了绝对定位标签的包含块。标签不占用普通文档流中的位置,因此卡片内容需要为标签预留足够空间。较复杂的文档中,变换等其他特征也可能建立包含块。解释 top 或 left 前,先找出实际参照盒。
粘性标题需要 top: 0 这类偏移值,以及足够的滚动空间。定位适合表达局部关系;页面的主要列,则更适合用弹性布局或网格等系统描述。
先建立窄版,再按空间扩展
响应式设计使用同一份内容结构,按可用空间调整呈现方式。应明确加入视口声明,让文字换行、限制阅读宽度,并让图像随容器缩放。
例题
只用本地 CSS 的完整响应式页面
这份文档内含样式表,可保存为 reading.html。窄视口下两区上下排列;从 48rem 起,两区并排。
<!doctype html>
<html lang="zh-Hans">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>读书小组:下一次讨论</title>
<style>
* { box-sizing: border-box; }
body {
margin: 0;
font: 1rem/1.6 system-ui, sans-serif;
color: #20354d;
background: #f4f6fa;
}
header, main, footer {
width: calc(100% - 2rem);
max-width: 64rem;
margin-inline: auto;
}
.session-layout { display: flex; flex-direction: column; gap: 1rem; }
.reading-card {
padding: 1rem;
border: 1px solid #b8c7d8;
border-radius: 0.5rem;
background: white;
}
.reading-card h2 { line-height: 1.2; }
@media (min-width: 48rem) {
.session-layout { flex-direction: row; }
.main-topic { flex: 2; }
.side-note { flex: 1; }
}
</style>
</head>
<body>
<header><h1>读书小组</h1></header>
<main class="session-layout">
<section class="reading-card main-topic">
<h2>下一次讨论</h2>
<p>比较两种组织短篇技术文章的方式。</p>
</section>
<aside class="reading-card side-note">
<h2>事前准备</h2>
<p>带来一段文字和一个问题。</p>
</aside>
</main>
<footer><p>讨论于 16:00 开始。</p></footer>
</body>
</html>
基础规则先建立窄版;媒体查询在条件成立时加入宽版,而不是复制另一份内容。flex: 2 与 flex: 1 使用不同增长因子分配剩余空间,内边距与边框也会影响最终可见盒子的尺寸。
栅格图像可使用 max-width: 100%; height: auto,在容器变窄时缩小,同时保留固有宽高比。picture 也可为较宽视口选择另一种构图。测试时,应检查每个布局变化点两侧的宽度,再尝试较长文字与较大的浏览器文字设置。
Bootstrap 为可复用布局规则命名
以下类的例子使用 Bootstrap 5.3 约定。本地文档可以先加载本地框架样式表,再加载自己的样式表:
<link rel="stylesheet" href="vendor/bootstrap.min.css">
<link rel="stylesheet" href="reading.css">
这些本地文件须先备好。Bootstrap CSS 包含 Reboot 基础样式、网格、工具类及组件样式;类名须配合相应样式表才会产生外观。
Bootstrap 默认网格在每一行使用十二个尺寸单位。容器界定内容区,行组织列与列间距,列的类则分配宽度。这不要求十二个内容元素,而容器本身也不会自动建立一行列。
<main class="container py-4">
<div class="row g-3">
<section class="col-12 col-md-8">
<div class="border rounded p-3">
<h2>下一次讨论</h2>
<p>阅读开头一节,指出其主要论点。</p>
</div>
</section>
<aside class="col-12 col-md-4">
<div class="border rounded p-3">
<h2>事前准备</h2>
<p>带来一个问题。</p>
</div>
</aside>
</div>
</main>
低于 md 断点时,每区使用一行的 12/12,因此上下排列。从 md 起,两区分别使用 8/12 与 4/12。g-3 提供列间距;边框与内边距放在内层盒子上,让可见表面之间保留该间距。响应式类会在更宽视口继续适用,直到另一条符合条件的较后断点规则改变设置。
| 默认层级 | 最小视口宽度 | 例子 |
|---|---|---|
| 超小 | 无最小值 | col-12 |
sm | 576 CSS 像素 | col-sm-6 |
md | 768 CSS 像素 | col-md-8 |
lg | 992 CSS 像素 | col-lg-4 |
xl | 1200 CSS 像素 | col-xl-3 |
xxl | 1400 CSS 像素 | col-xxl-2 |
这些类名没有 xs 中缀。.container 具有响应式最大宽度,.container-fluid 则保持全宽。单纯的 .col 与同级列平均分享可用宽度;.col-auto 按内容决定尺寸。可换行的行中,明确指定的列总跨度超过十二时,较后的列会移到下一行。
工具类与组件仍需要语义 HTML
工具类让细小规则可复用。在 Bootstrap 默认间距比例中,p-3 提供 1rem 内边距,mb-3 提供 1rem 下外边距,gap-3 则在适用布局中提供 1rem 间隙。p-2 p-md-4 让内边距在 md 改用另一等级设置。Bootstrap 5 的 ms-* 与 me-* 分别代表起始及末端间距;fst-italic 选择斜体。
HTML 中的类顺序不是优先列表:class="p-2 p-3" 不意味着“最后一个类胜出”。结果由样式表声明决定。许多工具类带有 !important,所以单把自定义 CSS 放在后面,未必足以覆盖它们。每个生效设置应选定一个工具类,再以组件自定义属性或范围合适的规则调整外观。
<nav class="nav nav-pills" aria-label="阅读章节">
<a class="nav-link active" aria-current="location" href="#plan">计划</a>
<a class="nav-link" href="#preparation">事前准备</a>
</nav>
<section id="plan"><h2>计划</h2></section>
<section id="preparation"><h2>事前准备</h2></section>
<button type="button" class="btn btn-primary">标记为待读</button>
按钮类为真正的按钮设置外观;动作本身仍需要相应行为。table table-striped table-hover 为语义表格添加样式,list-group 与 list-group-item 为列表添加样式,form-control 则设置表单控件外观。样式不会自动提供标签或验证规则。table-dark 这类工具应放在预期的表格区域,表头单元格仍使用 th。
部分交互组件,例如折叠、模态窗口及下拉菜单,需要 Bootstrap JavaScript。本地 bundle 可以提供所需插件及 Popper 支持。静态布局使用列、边框或按钮样式时,不需要因此加入脚本。响应式折叠导航栏除了 navbar-expand-lg,还需要匹配的折叠标记与行为。
Bootstrap Icons 是独立的图标集合。字体图标类需要本地图标样式表与字体文件,也可以改用 SVG。图标若只装饰旁边已有的可见文字,可以使用 aria-hidden="true";只有图标的控件,则需要描述动作的无障碍名称。框架可以不同,选择器、层叠与盒模型仍是共同基础。
动态效果描述随时间改变的值
变换改变元素绘制的方式,却不重新分配其普通文档流位置。translate、rotate、scale 与 skew 表达不同变换。过渡效果在可动画属性的值改变时,计算中间状态;关键帧动画则指定一段时间内的一系列值。
.session-link {
display: inline-block;
transition: transform 180ms ease;
}
.session-link:hover,
.session-link:focus-visible {
transform: translateY(-2px);
}
.session-indicator { animation: breathe 2s ease-in-out 2; }
@keyframes breathe {
0%, 100% { opacity: 0.65; }
50% { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
.session-link { transition: none; }
.session-indicator { animation: none; }
}
过渡规则明确指定要改变的属性,也支持键盘焦点状态。关键帧动画每次持续两秒,重复两次。减少动态效果规则移除动画,但内容与控件标签仍然存在。因为原有布局空间没有改变,变换后的盒子可能与旁边内容重叠。
练习
思考检查
练习 1:解释胜出的声明
一个带有 note 类的段落,位于 reading-card 类元素之内。样式表先写 .reading-card p { color: teal; },再写 .note { color: purple; }。两者都是普通、未分层作者规则。哪个颜色胜出?为什么把第二条移到较后的 style 元素,也不改变推理?
解答 · 练习 1 解答
蓝绿色胜出。.reading-card p 的优先级是 (0,1,1),.note 则是 (0,1,0)。在层叠的这个比较阶段,源码顺序只用来处理平手。移到较后的 style 元素,只改变顺序,不改变优先级或来源,因此在题目条件下仍会落败。
思考检查
练习 2:计算边框盒
盒子设有 width: 300px,左右内边距各 20px、左右边框各 3px、左右外边距各 10px。计算采用 content-box 时的边框盒宽度与水平总范围,再计算采用 border-box 时的内容宽度。
解答 · 练习 2 解答
采用 content-box 时,边框盒是 CSS 像素,水平总范围是 CSS 像素。采用 border-box 时,边框盒就是声明的 CSS 像素,内容宽度是 CSS 像素。外边距仍在该宽度以外。
思考检查
练习 3:预测响应式列
三个同级列都带有 col-12 col-md-6 col-xl-4。使用 Bootstrap 默认网格,解释视口宽度为 600、800 及 1250 CSS 像素时的排列。
解答 · 练习 3 解答
600 像素尚未达到 md,所以每列占十二单位,三列上下排列。800 像素适用 md:每列六单位,首行容纳两列,第三列换行。1250 像素适用 xl:每列四单位,三列可同行。断点名称针对视口宽度,而不是各列的宽度。
思考检查
练习 4:找出覆盖标签的位置
活动卡片内有绝对定位标签,设置为 top: 0.5rem; right: 0.5rem。解释卡片上的 position: relative 有什么作用,以及为什么文字可能需要在标签附近加入内边距。
解答 · 练习 4 解答
本例中的相对定位卡片建立标签的包含块,因此偏移值以该盒子为基准。绝对定位标签脱离普通文档流,不会在文字旁边预留位置;合适的内容间距可避免重叠。若没有该包含块,则可能改用另一祖先或初始包含块,实际结果取决于文档。
思考检查
练习 5:区分过渡效果与动画
在动态效果例子中,什么触发链接的过渡?指示元素的动画总共持续多少秒?减少动态效果媒体查询成立时,又会改变什么?
解答 · 练习 5 解答
链接进入或离开匹配的悬停或可见焦点状态时,transform 的值改变,从而触发过渡。指示元素重复两次,每次两秒,合共四秒。减少动态效果规则停用链接过渡及指示元素动画,但链接的状态样式与文档内容仍然存在。
本单元的布局规则,作用于语义化 HTML 文档所建立的文档结构。