楼层: 首页/ 软件技术/ 前端基础/ 第 3 章 CSS3:把页面穿得人模狗样
装

第 3 章 CSS3:把页面穿得人模狗样

CSS3 — Selectors, Box, Flex, Grid, Responsive

CSS 是前端最容易"一看就懂、一写就崩"的东西。语法很简单,选择器 { 属性: 值; },但布局和优先级是两个大坑。这一章把核心规则讲透:选择器、盒模型、Flex、Grid、定位、响应式、动画。

选择器:你想给谁加样式

写法选中谁
*所有元素。一般只用来 reset 边距。
p所有 <p> 元素。
.card所有 class="card" 的元素。最常用。
#headerid="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"> 提前拉。