装
第 3 章 CSS3:把页面穿得人模狗样
CSS3 — Selectors, Box, Flex, Grid, Responsive
CSS 是前端最容易"一看就懂、一写就崩"的东西。语法很简单,选择器 { 属性: 值; },但布局和优先级是两个大坑。这一章把核心规则讲透:选择器、盒模型、Flex、Grid、定位、响应式、动画。
选择器:你想给谁加样式
| 写法 | 选中谁 |
* | 所有元素。一般只用来 reset 边距。 |
p | 所有 <p> 元素。 |
.card | 所有 class="card" 的元素。最常用。 |
#header | id="header" 的元素。id 全页唯一。 |
.nav a | .nav 里面所有的 a(后代,不管隔几层)。 |
.nav > a | .nav 的直接子级 a。 |
input[type="text"] | 属性选择器。 |
a:hover | 伪类:鼠标悬停时。 |
li:nth-child(2n) | 偶数 li,斑马纹表格用。 |
.tip::before | 伪元素:在元素内容前插一个盒子。 |
CSS 的打架规则:优先级
同个元素被多条规则命中,谁说了算?记住这条从大到小:!important > 内联样式 style="…" > #id > .class / 伪类 > 元素标签。同级后面写的覆盖前面的。
新手最容易犯的错:用一堆 !important 压样式,结果越压越乱。正确做法是减少选择器嵌套,用 layer 或 BEM 命名把职责分清。
盒模型:每个元素都是一个盒子
浏览器把每个 HTML 元素都看成一个矩形盒子,从内到外:content(内容)→ padding(内边距)→ border(边框)→ margin(外边距)。
全局建议:一行 CSS 解决 90% 盒模型烦恼
/* 让 width 就是 width,别被 padding 撑大 */
*, *::before, *::after {
box-sizing: border-box; /* 默认 content-box 太反人类 */
}
/* margin 合并:两个相邻块的上下 margin 会合并成最大的那个,不是相加 */
.a { margin-bottom: 20px; }
.b { margin-top: 30px; } /* 实际间距是 30px,不是 50px */
Flexbox:一维布局神器
以前居中一个 div 要算半天,现在 Flex 两行搞定。把父容器设成 display:flex,里面的子元素就是"弹性项目"。
/* 经典居中:水平垂直都居中 */
.center {
display: flex;
justify-content: center; /* 主轴(默认横向)居中 */
align-items: center; /* 交叉轴(纵向)居中 */
height: 100vh;
}
.row {
display: flex;
gap: 12px; /* 子元素之间的间距,比 margin 香 */
justify-content: space-between; /* 两端对齐,中间留白 */
flex-wrap: wrap; /* 放不下就换行,别挤爆 */
}
关键属性记这几个:flex-direction(主轴方向)、justify-content(主轴对齐)、align-items(交叉轴对齐)、flex-grow(剩余空间怎么分)。一维布局用 Flex——排一行按钮、排一列卡片,闭眼上 Flex。
Grid:二维布局,画表格一样排版
要同时控制行和列(比如后台布局:左边栏 + 顶栏 + 内容区),用 Grid。
.admin {
display: grid;
grid-template-columns: 220px 1fr; /* 左栏 220px,右边吃掉剩余 */
grid-template-rows: 56px 1fr; /* 顶栏 56px,下面撑满 */
grid-template-areas:
"header header"
"sidebar main";
height: 100vh;
}
.header { grid-area: header; }
.sidebar { grid-area: sidebar; }
.main { grid-area: main; }
fr 是 Grid 专用单位,意思是"剩余空间的份数"。二维复杂排版用 Grid——页头页脚侧边栏三栏布局,三行代码搞定。
定位 position
| 值 | 效果 |
static | 默认。按正常文档流排,top/left 没用。 |
relative | 相对自己原来的位置偏移,还占着原来的坑。 |
absolute | 脱离文档流,相对最近的非 static 祖先定位。做弹层、小标签常用。 |
fixed | 相对浏览器窗口固定。回到顶部按钮、固定导航。 |
sticky | 滚动到位置后"粘住"。吸顶导航常用。 |
谁盖在谁上面由 z-index 决定,但只有定位元素(非 static)才吃 z-index。新手常忘这一点,z-index 写了 999 也没用。
响应式:手机上也得能看
一套网页要在手机、平板、桌面都能看。核心三件套:
- 移动优先:先写手机版样式,再用
@media (min-width: 768px) 往上加桌面样式。
- 弹性单位:
rem(相对根字号)、vw/vh(视口宽/高的 1%)、%。clamp(16px, 2vw, 24px) 字号自动缩放。
- 断点:常用 768px(平板)、1024px(桌面)。
/* 手机默认:单列 */
.cards { display: grid; grid-template-columns: 1fr; gap: 16px; }
/* 平板及以上:两列 */
@media (min-width: 768px) {
.cards { grid-template-columns: repeat(2, 1fr); }
}
/* 桌面:四列 */
@media (min-width: 1024px) {
.cards { grid-template-columns: repeat(4, 1fr); }
}
动起来:transition、animation、transform
.btn {
transition: transform .2s ease, box-shadow .2s ease;
/* 谁要动、动多久、缓动函数 */
}
.btn:hover {
transform: translateY(-2px) scale(1.03); /* 上浮 + 放大 */
box-shadow: 0 8px 20px rgba(0,0,0,.15);
}
/* 关键帧动画:loading 转圈 */
@keyframes spin {
to { transform: rotate(360deg); }
}
.spinner {
animation: spin 1s linear infinite;
}
transform 不触发重排,性能好,做位移动画优先用它,别改 top/left。
CSS 变量与现代新特性
:root {
--color-primary: #2E4B3D;
--color-bg: #F7F5EF;
--radius: 12px;
}
.card {
background: var(--color-bg);
border-radius: var(--radius);
}
/* 主题切换:JS 改 html[data-theme="dark"] 下的变量即可 */
html[data-theme="dark"] {
--color-bg: #1B1D1A;
}
近两年 CSS 又补了一堆好东西::has()(父选择器,比如"包含图片的卡片"自动改样式)、@container(根据组件宽度而非视口响应)、color-mix()(混色)、@layer(层叠优先级管理)、CSS 原生嵌套。这些不用一上来就背,知道有这玩意,需要时查 MDN。
CSS 常见坑
① 浮动不清除:老派 float 布局,父元素会塌。现在基本用 Flex/Grid,但看到老代码要懂。
② 边距合并:相邻块的 margin 不是相加,是取大。
③ 写一堆 !important:短期爽,长期乱。改用层叠上下文或提高选择器特异性。
CSS3 深化:选择器全表、盒模型、Flex/Grid、动画与新特性
前面教的是"够用",这里是"体系"。CSS 就是网页的化妆师——化妆师手上得有全套刷子,而不是只有一支。
选择器完整清单
| 类别 | 写法 |
| 基础 | * 通配、p 元素、.c 类、#id。 |
| 组合 | .a .b 后代、.a > .b 直接子代、.a + .b 相邻兄弟、.a ~ .b 通用兄弟。 |
| 属性 | [disabled]、[type=text]、[class~=a](含 a 词)、[lang|=zh](zh 或 zh-CN)、[src^=https] 开头、[src$=.png] 结尾、[href*=x] 包含。 |
| 状态伪类 | :hover :active :focus :focus-visible :focus-within :visited :link。 |
| 结构伪类 | :first-child :last-child :only-child :nth-child(2n+1) :nth-of-type(odd) :empty :root :target。 |
| 表单伪类 | :checked :disabled :enabled :required :valid :invalid :in-range :out-of-range。 |
| 逻辑伪类 | :not(.x)、:is(.a, .b)、:where(.a, .b)(优先级永远 0)、:has()(父选择器:li:has(> .done))。 |
| 伪元素 | ::before ::after ::first-letter ::first-line ::selection ::placeholder ::marker ::backdrop。 |
:has() 父选择器实战:卡片含图片才加图边距
/* 只有里面有 img 的卡片才加 padding-bottom,纯文字卡片不加 */
.card:has(img) { padding-bottom: 0; }
/* 复选框勾上了,父 li 变灰 */
li:has(input:checked) { opacity: .5; }
/* 输入框聚焦时,包它的 .field 边框高亮 */
.field:has(input:focus) { border-color: #2E4B3D; }
盒模型深入
/* content-box(默认):width 只是内容宽,padding/border 另外加 */
/* border-box:width 包含 padding+border,所见即所得,强烈推荐 */
* { box-sizing: border-box; }
/* 阴影:x 偏移 y 偏移 模糊 扩散 颜色;inset 内阴影;可叠多层 */
.card {
box-shadow: 0 4px 12px rgba(0,0,0,.1), /* 外阴影 */
inset 0 1px 0 #fff; /* 顶部高光 */
}
.text { text-shadow: 0 1px 2px rgba(0,0,0,.2); }
/* outline 不占空间,画在 border 外面;做 focus 圈比 border 好(不会挤布局)*/
button:focus-visible { outline: 2px solid #B98A2F; outline-offset: 2px; }
叠BFC 与 margin 折叠
垂直 margin 折叠:两个块上下相邻,margin 不是相加而是取最大。父子块之间若没有 border/padding/inline 隔开,子的 margin 会"穿透"到父外面。
BFC(块级格式化上下文):一个独立渲染区域,内部布局不影响外部,且不会发生 margin 折叠、能包住浮动子元素。触发方式:overflow: hidden/auto(非 visible)、display: flow-root(最干净)、flex/grid 子项、浮动元素。想让父元素包住浮动子元素,给它 display:flow-root。
Flexbox 全属性
| 位置 | 属性 |
| 容器 | display:flex、flex-direction: row|column|row-reverse、flex-wrap: wrap、justify-content(主轴:flex-start/center/space-between/space-around)、align-items(交叉轴:stretch/center/flex-start/baseline)、align-content(多行交叉轴分布)、gap。 |
| 项目 | order(排序)、flex-grow(吃剩余空间份数)、flex-shrink(空间不足时压缩比例)、flex-basis(初始尺寸)、flex(缩写 grow shrink basis)、align-self(单个项目覆盖 align-items)。 |
Grid 全属性与函数
.layout {
display: grid;
/* repeat 重复、minmax 最小最大、auto-fill 自动换行 */
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
gap: 16px;
/* 命名区域,画布局像填格子 */
grid-template-areas:
"head head"
"side main"
"foot foot";
}
.area-head { grid-area: head; }
/* 项目落位:跨几列几行 */
.span2 { grid-column: span 2; grid-row: 1; }
/* 函数:fr 剩余份数、minmax() 区间、fit-content() 内容最大尺寸 */
/* auto-fill 留空轨道;auto-fit 把空轨道压扁、内容拉伸 */
| 布局选型 | 怎么选 |
| 一维 | 排一行/一列(导航、按钮组、列表)→ Flexbox。 |
| 二维 | 行列同时控制(卡片墙、后台三栏、整页骨架)→ Grid。 |
| float | 现在只用于"文字绕排图片",别用来做整页布局。 |
| position | 浮层、吸顶、角标用 absolute/fixed/sticky。 |
响应式与流式排版
/* 移动优先:先写手机,再 min-width 往上加 */
.card { font-size: 16px; }
/* 断点(业界 Tailwind 默认):sm640 md768 lg1024 xl1280 2xl1536 */
@media (min-width: 768px) { .card { font-size: 17px; } }
@media (min-width: 1024px) { .card { font-size: 18px; } }
/* 按用户偏好响应 */
@media (prefers-color-scheme: dark) { :root { --bg: #1B1D1A; } }
@media (prefers-reduced-motion: reduce) { * { animation: none; } }
@media (hover: hover) { .btn:hover { ... } } /* 只有真有 hover 的设备才加 hover */
/* 流式字号:最小16,理想2vw,最大24,再也不用写一堆媒体查询 */
h1 { font-size: clamp(1.25rem, 2.5vw, 2rem); }
/* 响应式图片:浏览器按宽度选最合适的一张 */
<img
srcset="small.jpg 400w, large.jpg 800w"
sizes="(max-width:600px) 400px, 800px"
src="large.jpg" alt="...">
<!-- <picture> 可以按媒体条件/格式切换整个 source -->
<picture>
<source media="(max-width:600px)" srcset="mobile.jpg">
<img src="desktop.jpg" alt="...">
</picture>
过渡与动画
.btn {
/* property duration timing-function delay */
transition: transform .25s cubic-bezier(.4,0,.2,1), box-shadow .25s;
}
.btn:active { transform: scale(.97); } /* 按下回弹 */
/* 3D 变换:perspective 给父元素,子元素才有纵深 */
.card3d { perspective: 800px; }
.card3d .inner { transform-style: preserve-3d; transition: transform .6s; }
.card3d:hover .inner { transform: rotateY(180deg); backface-visibility: hidden; }
/* 关键帧动画 */
@keyframes bounce {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-12px); }
}
.ball { animation: bounce 1s ease-in-out infinite; }
/* will-change: 告诉浏览器提前优化(别滥用,占 GPU 层)*/
.animated { will-change: transform; }
Sass / SCSS 预处理器
// variables.scss —— 变量、嵌套、混合、模块化
$primary: #2E4B3D;
$radius: 12px;
@mixin flex-center { // 混合:复用一段声明
display: flex;
justify-content: center;
align-items: center;
}
.card {
$pad: 16px; // 局部变量
padding: $pad;
border-radius: $radius;
background: lighten($primary, 40%); // 内置函数
.title { color: $primary; } // 嵌套,编译成 .card .title
&:hover { transform: translateY(-2px); } // & 引用父选择器
}
%btn-base { ... } // 占位符:只有被 @extend 才输出
.btn-primary { @extend %btn-base; }
// 编译:sass input.scss output.css
现代项目里 Sass 用得少了(CSS 原生变量、嵌套、Tailwind 替代了大部分场景),但老项目和组件库里遍地都是,得能看懂。
CSS 新特性速览
- CSS Nesting 原生:不用 Sass 也能写嵌套,
.card { .title {} }(注意嵌套后代选择器要写 & 或空格)。
- 容器查询 @container:按"组件自己的宽度"而非视口布局,组件可复用进任何容器。
- color-mix(red, blue 50%):颜色混色,做主题色阶神器。
- oklch():感知均匀的新颜色空间,比 rgb/hsl 调出来的渐变更自然。
- accent-color: #2E4B3D:一行给复选框/单选/滑块染主题色。
- backdrop-filter: blur(8px):毛玻璃效果(导航栏磨砂)。
- @layer 层叠层:
@layer reset, base, components; 显式控制谁压谁,告别 !important。
综合案例:导航栏 + 卡片网格 + 动画按钮
<style>
@layer reset, layout, components;
@layer reset { * { margin:0; box-sizing:border-box; } }
@layer layout {
.topnav { display:flex; justify-content:space-between; align-items:center;
padding:0 24px; height:56px; background:#2E4B3D; }
.grid-cards { display:grid; gap:16px; margin:24px;
grid-template-columns:repeat(auto-fill,minmax(220px,1fr)); }
}
@layer components {
.nav-link { color:#F3E9D3; text-decoration:none; padding:8px 12px; border-radius:8px; }
.nav-link:hover { background:rgba(255,255,255,.12); }
.card { background:#fff; border-radius:12px; padding:16px;
box-shadow:0 2px 8px rgba(0,0,0,.08);
transition:transform .2s, box-shadow .2s; }
.card:hover { transform:translateY(-4px); box-shadow:0 8px 20px rgba(0,0,0,.12); }
.btn { padding:8px 16px; border:none; border-radius:8px; background:#B98A2F;
color:#fff; cursor:pointer; transition:filter .15s; }
.btn:hover { filter:brightness(1.1); }
.btn:active { transform:scale(.96); }
}
</style>
本章面试题(CSS)
1. 盒模型 content-box 和 border-box 区别?为什么推荐 border-box?
看答案
content-box(W3C 默认)的 width 只含内容,padding/border 额外把盒子撑大;border-box 的 width 包含 content+padding+border,设 width:100% 就是真的 100%,不会被内边距撑爆。所以全局 *{box-sizing:border-box} 省掉无数布局计算。
2. 什么是 BFC?怎么触发?解决什么问题?
看答案
BFC 是独立的块级渲染容器:内部 margin 不与外部折叠、能包住浮动元素、不与浮动兄弟重叠。触发:overflow 非 visible、display:flow-root、flex/grid 子项、浮动、绝对定位。用途:清除浮动、阻止 margin 穿透。
3. Flex 和 Grid 怎么选?
看答案
Flex 是一维(只管一行或一列),适合导航、按钮组、居中等;Grid 是二维(行列同时定义),适合整页骨架、卡片墙、不规则网格。实际常混用:外层 Grid 定大区域,区域内部用 Flex 排内容。
4. z-index 不生效通常是什么原因?层叠上下文怎么理解?
看答案
常见原因:元素没定位(非 static)不参与 z-index 排序;祖先形成了层叠上下文(transform/filter/opacity<1/z-index 不为 auto 的定位元素),子元素的 z-index 只在祖先内部比,出不去。层叠上下文像"房间",每个房间内部独立排名,房间之间再按各自的 z-index 比。
5. 为什么动画优先用 transform/opacity 而不是 top/left?
看答案
改 top/left 会触发 layout(重排)→ repaint,每帧都要重新计算布局,掉帧;transform 和 opacity 只触发 composite(合成层),GPU 直接处理,不碰布局和绘制,60fps 稳。
Web 字体:让用户看到你设计的那套字
系统字体(system-ui / 苹方 / 微软雅黑)不下载、零等待,但设计师想要定制字体就得用 @font-face 把字体文件发给浏览器。核心就一句:告诉浏览器"这个字体文件在哪个 URL、叫什么名、什么时候开始用"。
@font-face 声明 + font-display 避免"文字看不见"
/* 自定义一个字体名叫 BrandFont,文件放字体目录 */
@font-face {
font-family: "BrandFont";
src: url("/fonts/Brand.woff2") format("woff2"); /* woff2 体积最小,现代浏览器全支持 */
font-weight: 400 700; /* 一个文件覆盖多字重(可变字体更佳) */
font-display: swap; /* 关键:字体没下完先用系统字顶着,下好再替换 */
}
/* 用它:先定制字体,再回退系统字体,最后兜底 sans-serif */
body { font-family: "BrandFont", "PingFang SC", "Microsoft YaHei", sans-serif; }
| font-display 取值 | 行为 |
swap | 字体加载期间先用后备字体显示,下好后交换。推荐,文字永远不白屏。 |
block | 短暂"文字不可见"(FOIT),等字体。首屏慢。 |
optional | 能快速加载就用,否则这次访问就用系统字。适合低频排版字体。 |
性能与防坑:① 只加载用到的字符集——中文动辄几 MB,用工具(如 fontmin)按页面用到的汉字子集化,不然首屏卡死;② 优先 woff2,体积比 ttf/otf 小 30%+;③ 字体文件也可以 <link rel="preload"> 提前拉。