本章从「网页到底是什么」讲起,手把手带你掌握语义化 HTML、盒模型、Flexbox、Grid、响应式设计与动画。学完本章,你能独立把任何原型图还原成静态页面。
先建立全局认知:网页从哪来,浏览器又做了什么。
你在浏览器输入网址,本质上是做了一次「请求-响应」的往返:
// 你(客户端) // 服务器
你输入网址 → 浏览器把网址解析为请求 → 发给服务器
→ 服务器返回 HTML 文件(+CSS+JS)
浏览器拿到 HTML → 解析 → 渲染成你看到的页面
关键认知:网页的「原材料」是一段文本(HTML 代码),浏览器负责把它翻译成可视化的页面。服务器上存的是代码文件,你看到的是渲染结果。
以 https://www.example.com:443/path/page.html?name=zhangsan#top 为例:
| 部分 | 示例 | 含义 |
|---|---|---|
| 协议 | https:// | 怎么通信(还有 http://、file://) |
| 域名 | www.example.com | 服务器是谁(人话地址) |
| 端口 | :443 | 服务器的哪个门(https 默认 443,可省略) |
| 路径 | /path/page.html | 服务器上的哪个文件 |
| 查询参数 | ?name=zhangsan | 传给页面的数据(键=值) |
| 锚点 | #top | 页面内定位到 id 为 top 的元素 |
| 语言 | 类比 | 职责 |
|---|---|---|
| HTML | 骨骼 | 定义结构:页面上有哪些内容块(标题、段落、按钮…) |
| CSS | 皮肤 | 定义样式:每个内容块长什么样(颜色、大小、位置…) |
| JavaScript | 肌肉 | 定义行为:点击按钮后发生什么、数据怎么变化 |
打开任意编辑器(推荐 VS Code),新建文件 index.html,输入:
<!-- index.html -->
<!DOCTYPE html> <!-- 告诉浏览器:这是现代 HTML5 文档 -->
<html lang="zh-CN"> <!-- 根元素,lang 声明页面语言 -->
<head> <!-- 头部:放页面的「元信息」,不显示在页面上 -->
<meta charset="UTF-8"> <!-- 字符编码,中文不乱码的关键 -->
<title>我的第一个页面</title> <!-- 浏览器标签页上的标题 -->
</head>
<body> <!-- 主体:所有用户能看到的内容都在这 -->
<h1>你好,世界!</h1>
<p>这是我用 HTML 写的第一个页面。</p>
</body>
</html>
保存后用浏览器打开这个文件(双击即可),你就能看到「你好,世界!」。恭喜,你已经有第一个网页了。
掌握标签语法、文档结构、常用标签与语义化。
HTML 由「元素」组成,元素用「标签」标记,标签可以带「属性」:
<!-- 开始标签 属性 内容 结束标签 -->
<a href="https://x.com" target="_blank"> 点我 </a>
↑ ↑ ↑
标签名 属性(名="值") 内容
<img>、<br>、<hr>。<div><p>对</p></div>。| 标签 | 作用 | 示例效果 |
|---|---|---|
| <h1>~<h6> | 标题(1 最大,6 最小) | 大标题 → 小标题 |
| <p> | 段落 | 一段文字 |
| <a> | 超链接 | 链接 |
| <img> | 图片(单标签) | 🖼️ |
| <ul>/<li> | 无序列表 | • 条目 |
| <ol>/<li> | 有序列表 | 1. 条目 |
| <strong> | 加粗(语义:重要) | 重要 |
| <em> | 斜体(语义:强调) | 强调 |
| <br> | 换行(单标签) | 换 行 |
| <hr> | 水平分割线(单标签) | ———— |
<pre>,想强制空格用 。
<!-- 图片:src 是来源,alt 是图片加载失败时显示的文字(无障碍必需) -->
<img src="images/logo.png" alt="公司 logo" width="120">
<!-- 链接:href 目标;target="_blank" 新标签页打开 -->
<a href="https://example.com" target="_blank" rel="noopener">外部链接</a>
<a href="mailto:me@x.com">发邮件</a>
<a href="#s3">跳到本页 s3 区域</a> <!-- 锚点链接:目标元素的 id -->
target="_blank" 时必须加 rel="noopener",否则有被钓鱼攻击(反向 tabnabbing)的安全风险。这是面试也爱考的点。
<table>
<thead> <!-- 表头区 -->
<tr> <!-- 行 -->
<th>姓名</th><th>年龄</th> <!-- th:表头单元格 -->
</tr>
</thead>
<tbody> <!-- 数据区 -->
<tr><td>张三</td><td>25</td></tr> <!-- td:普通单元格 -->
</tbody>
</table>
合并单元格:colspan="2" 横向合并、rowspan="2" 纵向合并。
「语义化」= 用含义正确的标签表达内容,而不是用一堆 <div> 包一切。看一个对比:
<!-- ❌ 反面:全是 div -->
<div class="header">网站标题</div>
<div class="nav">导航链接</div>
<div class="main">正文内容</div>
<div class="footer">版权信息</div>
<!-- ✅ 正面:语义化 -->
<header>网站标题</header>
<nav>导航链接</nav>
<main>正文内容</main>
<footer>版权信息</footer>
| 标签 | 含义 |
|---|---|
| <header> | 页眉/区域头部 |
| <nav> | 导航 |
| <main> | 页面主体(一个页面只有一个) |
| <section> | 一个章节(通常带标题) |
| <article> | 独立成篇的内容(可脱离页面单独理解) |
| <aside> | 侧边栏/补充内容 |
| <footer> | 页脚 |
| <figure>+<figcaption> | 配图及说明文字 |
| <time> | 时间 |
<form action="/submit" method="post">
<!-- label 的 for 与 input 的 id 绑定:点文字也能聚焦输入框 -->
<label for="username">用户名</label>
<input type="text" id="username" name="username" placeholder="请输入" required>
<label for="pwd">密码</label>
<input type="password" id="pwd" name="pwd">
<!-- 单选框:同 name 的 radio 互斥 -->
<input type="radio" name="gender" value="male" id="m"><label for="m">男</label>
<input type="radio" name="gender" value="female" id="f"><label for="f">女</label>
<!-- 下拉框 -->
<select name="city">
<option value="beijing">北京</option>
<option value="shanghai">上海</option>
</select>
<button type="submit">提交</button>
</form>
| input 类型 | 作用 | 注意 |
|---|---|---|
| text | 单行文本 | 最常用 |
| password | 密码(打码显示) | 仅显示掩码,不加密传输 |
| email / number / url | 带格式校验的输入 | 手机端会弹出对应键盘 |
| checkbox | 复选框(可多选) | 同 name 成组 |
| radio | 单选框(互斥) | 同 name 才互斥 |
| file | 文件选择 | 配合 enctype="multipart/form-data" |
| date / color / range | 日期/颜色/滑块 | 浏览器原生控件 |
| hidden | 隐藏字段 | 用户不可见但随表单提交 |
选择器、层叠与优先级、单位、颜色。
<!-- ① 外部样式表(推荐,复用+缓存) -->
<link rel="stylesheet" href="style.css">
<!-- ② 页面内 style 标签(单页面小项目可用) -->
<style> p { color: red; } </style>
<!-- ③ 行内样式(不推荐,难维护、优先级最高要小心) -->
<p style="color: red;">文本</p>
基本语法:选择器 { 属性: 值; 属性: 值; },每条声明以分号结尾。
| 选择器 | 写法 | 选中什么 |
|---|---|---|
| 标签 | p | 所有 <p> |
| 类 | .card | 所有 class="card"(可复用) |
| ID | #nav | id="nav"(一个页面只能有一个) |
| 属性 | [type="text"] | type 属性为 text 的元素 |
| 后代 | div p | div 内部所有 p(不限层级) |
| 子代 | div > p | div 的直接子级 p |
| 相邻兄弟 | h1 + p | 紧跟在 h1 后面的第一个 p |
| 通用兄弟 | h1 ~ p | h1 后面的所有 p |
| 伪类 | :hover / :focus / :first-child / :nth-child(2n) | 状态或位置 |
| 伪元素 | ::before / ::after / ::first-line | 元素的局部/虚拟元素 |
| 通配 | * | 所有元素(重置样式常用) |
| 组合 | .card.active | 同时有 card 和 active 两个类 |
/* 伪类 vs 伪元素的本质区别 */
/* 伪类:一个冒号,描述元素的状态/位置 —— 元素本身就存在 */
a:hover { color: red; }
li:nth-child(2n) { background: #f0f0f0; }
/* 伪元素:两个冒号,创建 DOM 中不存在的虚拟元素 */
.card::before { content: "★ "; } /* 在 card 内容前插入星星 */
.card::after { content: ""; display: block; } /* 清除浮动常用技巧 */
同一个元素被多条规则命中时,谁生效?规则是:先比权重,权重相同比谁在后面。
/* 权重(从高到低): */
// 1. 行内样式 style="" 权值最高
// 2. ID 选择器 #nav 记 100 分
// 3. 类/伪类/属性 .card :hover [type] 记 10 分
// 4. 标签/伪元素 p ::before 记 1 分
// 5. * 通配符 0 分
/* 例:下面两条都作用于同一个 p,谁生效? */
#content p { color: blue; } /* 100 + 1 = 101 分 */
.card p { color: red; } /* 10 + 1 = 11 分 → 蓝色生效 */
color: red !important;)直接提到最高优先级。用它是「最后手段」,滥用会让调试变成地狱,面试官听到你用 !important 都会皱眉。| 单位 | 含义 | 典型用法 |
|---|---|---|
| px | 物理像素(固定) | 边框、小尺寸 |
| em | 相对父级的字体大小 | 1em = 父元素 font-size |
| rem | 相对根元素(html)的字体大小 | 全局字号,改 html 字号整体缩放 |
| vw / vh | 视口宽/高的 1% | 全屏背景 100vh |
| % | 相对父元素的对应属性 | 宽度 50% |
| fr(Grid) | 网格剩余空间分配 | 1fr 1fr = 两等分 |
/* 颜色的四种写法 */
color: red; /* 关键字 */
color: #ff0000; /* 十六进制(常用) */
color: rgb(255, 0, 0); /* 红绿蓝 0-255 */
color: rgba(255, 0, 0, 0.5); /* 加透明度 0-1 */
color: hsl(0, 100%, 50%); /* 色相/饱和度/明度 */
CSS 里每个元素都是一个「盒子」,理解盒子是理解布局的第一步。
从外到内:margin(外边距)→ border(边框)→ padding(内边距)→ content(内容)。
/* 一个盒子 = 内容区 + padding + border + margin */
.box {
width: 200px; /* 默认只指 content 的宽 */
padding: 20px; /* 内容与边框的距离 */
border: 2px solid #333; /* 边框:粗细 样式 颜色 */
margin: 10px; /* 盒子与外面元素的距离 */
}
/* 这个盒子实际占据宽度 = 200 + 20*2 + 2*2 + 10*2 = 264px */
* { box-sizing: border-box; }
这样 width 就是「border + padding + content」的总宽,所见即所得。
两个兄弟盒子垂直方向的外边距会合并(取较大值),而不是相加:
/* 上面盒子 margin-bottom: 30px,下面盒子 margin-top: 20px */
/* 实际间距 = 30px(取大者),不是 50px —— 这叫外边距塌陷 */
.a { margin-bottom: 30px; }
.b { margin-top: 20px; }
// 垂直方向才会塌陷,水平方向不会。BFC 可以阻止塌陷(见下节)
还有父子塌陷:子元素设置 margin-top 有时会「带跑」父元素。规避方案:父元素用 padding 代替、加 overflow 或开启 flex/grid 布局(flex/grid 容器内不会塌陷)。
BFC 是一个「独立结界」:结界内部的布局不影响外部。触发 BFC 的常见方式:overflow: hidden、display: flow-root、display: flex/grid、position: absolute/fixed。
理解元素默认怎么排,才能知道怎么让它「不按默认排」。
不设置任何定位时,元素按 HTML 顺序排列:块级元素从上往下堆,行内元素从左往右排(一行放不下自动换行)。这就是文档流,是布局的「默认状态」。
float: left/right 让元素「漂」到一侧,后续内容环绕它。它曾是布局主流,但缺陷多(高度塌陷、清除浮动麻烦),现在布局请用 Flexbox / Grid,浮动只在特殊场景(如图文环绕)使用。
/* 图文环绕:图片左浮动,文字环绕其右侧 */
img.avatar { float: left; margin-right: 16px; }
| 值 | 相对于谁定位 | 是否脱离文档流 | 用途 |
|---|---|---|---|
| static | 默认,无定位 | 否 | 默认 |
| relative | 自己原来的位置 | 否(占位保留) | 微调位置、作为 absolute 的锚点 |
| absolute | 最近的「已定位祖先」(relative/absolute/fixed),没有则到 body | 是 | 弹层、角标、图标定位 |
| fixed | 视口(屏幕),滚动不动 | 是 | 吸顶导航、悬浮按钮 |
| sticky | 滚动到阈值前是相对定位,到阈值后像 fixed 粘住 | 混合 | 表格吸顶表头、侧边栏跟随 |
/* 经典用法:父 relative + 子 absolute,把角标定位到右上角 */
.badge-wrap { position: relative; } /* 锚点 */
.badge {
position: absolute;
top: -6px; right: -6px; /* 相对锚点偏移 */
}
/* 子元素配合 left/top/right/bottom 指定位置,或 50%+transform 居中 */
.center {
position: absolute;
left: 50%; top: 50%;
transform: translate(-50%, -50%); /* 经典水平垂直居中 */
}
脱离文档流的元素可以叠放,叠放顺序由 z-index 决定(数值越大越靠上)。但注意:z-index 只在同一层叠上下文内比较。开启层叠上下文的常见方式:position + z-index、transform、opacity、filter。
/* 陷阱:父元素 A 的 z-index 比 B 小,A 里的子元素 z-index 再大也盖不住 B */
.a { position: relative; z-index: 1; }
.b { position: relative; z-index: 2; }
.a .child { position: relative; z-index: 999; } /* 999 也盖不过 .b,因为 .a 整体在下面 */
Flexbox 解决「一行/一列内如何排列」的问题,是 90% 布局场景的答案。
/* 给父元素加 display:flex,它就变成弹性容器 */
.container {
display: flex;
/* flex-direction 决定主轴方向: */
flex-direction: row; // 默认:水平排列(主轴=水平,交叉轴=垂直)
// flex-direction: column; // 垂直排列(主轴=垂直)
// flex-direction: row-reverse / column-reverse // 反向
}
/* 容器里的直接子元素自动变成「弹性项目」 */
掌握这张概念图,Flexbox 就懂了一半:主轴方向由 flex-direction 决定,交叉轴永远垂直于主轴。所有对齐属性都是围绕这两条轴说的。
| 属性 | 作用 | 常用值 |
|---|---|---|
| flex-direction | 主轴方向 | row / column / row-reverse / column-reverse |
| justify-content | 主轴方向对齐 | flex-start / center / space-between / space-around / space-evenly |
| align-items | 交叉轴方向对齐 | stretch(默认拉伸) / flex-start / center / flex-end / baseline |
| flex-wrap | 是否换行 | nowrap(默认不换) / wrap |
| align-content | 多行时的整体对齐(只有 wrap 才有效果) | 同 justify-content |
| gap | 项目间距(也可单独 gap-row / gap-column) | gap: 16px |
/* 一行代码实现水平垂直居中 —— 最常用的布局 */
.center-box {
display: flex;
justify-content: center; /* 主轴居中 */
align-items: center; /* 交叉轴居中 */
}
/* 导航栏经典布局:左 logo,中间菜单,右按钮 */
.nav {
display: flex;
justify-content: space-between; /* 两端撑开 */
align-items: center; /* 垂直居中 */
}
/* flex-grow(放大比例) flex-shrink(缩小比例) flex-basis(基础宽度) */
.item1 { flex: 1; } /* 等价于 1 1 0%:占满剩余空间的 1 份 */
.item2 { flex: 2; } /* 占 2 份 → 经典等分布局 */
/* 常见取值速查: */
// flex: 1 → 均分剩余空间(自适应)
// flex: none → 0 0 auto 不伸缩,按内容宽 */
// flex: auto → 1 1 auto 按内容伸缩 */
// flex-basis: 200px → 初始宽度 200px,空间多余再分配 */
width 常常「不听话」,因为默认 flex-shrink: 1 允许压缩。想让子项固定宽:加 flex-shrink: 0 或直接写 flex: none。
Flex 管一维(一行或一列),Grid 管二维(行和列同时控制)。
.grid {
display: grid;
/* 定义 3 列:第一列 200px 固定,其余两列平分剩余空间 */
grid-template-columns: 200px 1fr 1fr;
/* 定义行高 */
grid-template-rows: 100px auto;
gap: 16px; /* 行列间距 */
}
fr:分数单位,表示剩余空间的份额(1fr 1fr = 两等分)。repeat(3, 1fr):重复 3 份 1fr,等价 1fr 1fr 1fr。auto:按内容大小。minmax(200px, 1fr):最小 200px,最多 1fr——响应式神器。.grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; }
/* 让某个项目跨两列(网格线编号从 1 开始) */
.item-wide { grid-column: 1 / 3; } /* 从第 1 条线到第 3 条线 → 占 2 列 */
/* 等价写法 */
.item-wide { grid-column: span 2; } /* 跨 2 列 */
/* 显式指定行列位置 */
.item-special {
grid-column: 2 / 4; /* 第 2 到第 4 条线 */
grid-row: 1 / 2; /* 第一行 */
}
更高级的用法是 grid-template-areas 用「画图」的方式布局,适合整体页面骨架:
.layout {
display: grid;
grid-template-columns: 200px 1fr;
grid-template-areas:
"header header"
"sidebar main"
"footer footer";
}
.header { grid-area: header; }
.sidebar { grid-area: sidebar; }
.main { grid-area: main; }
.footer { grid-area: footer; }
grid-auto-rows: 100px 控制。justify-items/justify-content 管水平,align-items/align-content 管垂直(用法和 Flex 类似)。place-items: center:同时设置两个方向,一行代码居中。一套代码,在手机、平板、电脑上都好看。
<!-- 告诉手机浏览器:按设备真实宽度渲染,初始缩放 1:1 -->
<meta name="viewport" content="width=device-width, initial-scale=1.0">
没有这行,手机浏览器会按 980px 的宽度渲染再缩小,字小得看不清。这是移动端第一课。
/* 默认样式(移动优先写法:先写手机版) */
.card { display: flex; flex-direction: column; }
/* 屏幕宽度 ≥ 768px 时(平板) */
@media (min-width: 768px) {
.card { flex-direction: row; }
}
/* 屏幕宽度 ≥ 1200px 时(桌面) */
@media (min-width: 1200px) {
.card { padding: 32px; }
}
min-width 逐级增强,代码更少更清晰。/* 宽度用 % 或 fr,随视口伸缩 */
.container { width: 90%; max-width: 1200px; margin: 0 auto; }
/* 图片最大不超过父容器 */
img { max-width: 100%; height: auto; }
/* 现代方案:容器查询(container query),按组件自身宽度响应 */
.card-list { container-type: inline-size; }
@container (min-width: 400px) {
.card { flex-direction: row; }
}
从「会写」到「写好」,这些能力是分水岭。
/* 定义:在 :root 上定义全局变量 */
:root {
--color-primary: #2563eb;
--space-md: 16px;
--radius: 8px;
}
/* 使用:var() 引用,还支持带默认值 */
.button {
background: var(--color-primary, #333); /* 找不到时用 #333 */
padding: var(--space-md);
border-radius: var(--radius);
}
/* 主题切换:改一个变量,全站变色 */
[data-theme="dark"] { --color-primary: #60a5fa; }
CSS 变量是设计系统(Design Token)的基石:颜色、间距、圆角全部收敛成变量,改一处全局生效,主题切换轻松实现。这是资深工程师的标配写法。
/* 过渡:属性值变化时平滑过渡,而不是瞬间跳变 */
.button {
background: #2563eb;
transition: background 0.3s ease, transform 0.2s ease;
}
.button:hover {
background: #1d4ed8;
transform: translateY(-2px); /* 悬停上浮 2px */
}
/* 动画:keyframes 定义关键帧,animation 引用 */
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
.fade-in { animation: fadeIn 0.5s ease forwards; }
// animation: 名称 时长 缓动 是否保持结束状态;forwards 保持最后一帧
transform 和 opacity,这两个属性由 GPU 合成,不触发回流重绘;不要动画 width/height/margin/top,会每帧触发布局计算,卡顿。
.el {
transform: translate(10px, 20px); /* 平移 */
transform: scale(1.2); /* 缩放 */
transform: rotate(45deg); /* 旋转 */
transform: skew(10deg); /* 斜切 */
transform-origin: center; /* 变换原点,默认中心 */
}
BEM = 块(Block)+ 元素(Element)+ 修饰符(Modifier),命名格式:block__element--modifier。
/* 块:独立组件 */
/* 元素:块内部的一部分,用 __ 连接 */
/* 修饰符:状态变体,用 -- 连接 */
.card { } /* 块 */
.card__title { } /* 元素:标题 */
.card__button--primary { } /* 修饰符:主按钮变体 */
// HTML: <div class="card"><h3 class="card__title">...</h3>...</div>
好处:样式不会互相污染(天然「作用域」)、可读性高、团队协作零沟通成本。
把最容易翻车的点集中在一起,写代码时对照自查。
| 坑 | 现象 | 解决 |
|---|---|---|
| 没加 viewport meta | 手机上看字小得看不清 | head 里加 viewport 标签 |
| 中文乱码 | 页面显示「锟斤拷」 | 加 <meta charset="UTF-8">,文件保存用 UTF-8 |
| 没有 box-sizing: border-box | 设了宽度却比预期宽 | 全局重置 * { box-sizing: border-box } |
| 外边距塌陷 | 两个盒子距离比预期的窄 | 父级加 overflow:hidden 或改 flex/grid |
| absolute 定位乱跑 | 子元素定位到了页面角落 | 父元素加 position: relative 作为锚点 |
| flex 子项宽度失控 | 设置了 width 却变了 | 加 flex-shrink: 0 或 flex: none |
| 选择器权重混乱 | 样式「不生效」 | 层级用类选择器,保持权重扁平 |
| 动画卡顿 | 动画掉帧 | 只用 transform/opacity 做动画 |
| 图片变形 | 图片被拉伸 | img { max-width:100%; height:auto } 或 object-fit: cover |
| 文字溢出省略 | 标题过长换行难看 | 单行省略三件套(见下) |
/* 单行文字省略(背下来,高频) */
.ellipsis {
overflow: hidden; /* 隐藏溢出 */
white-space: nowrap; /* 不换行 */
text-overflow: ellipsis; /* 显示省略号 */
}
/* 多行省略(-webkit- 前缀) */
.ellipsis-2 {
display: -webkit-box;
-webkit-line-clamp: 2; /* 显示 2 行 */
-webkit-box-orient: vertical;
overflow: hidden;
}
两个项目都独立完成,阶段一才算通关。
要求:
验收:手机、平板、电脑三个宽度下都布局正常;打开 DevTools 移动模拟无横向滚动条。
选一个你常用的网站(比如新闻门户、产品官网),用 HTML + CSS 还原它的首页静态结构。不要求一模一样,但要求:
这个项目会逼你把本章所有知识点串起来,做完你会发现自己突然「会布局了」。
用「讲给别人听」的方式回答。卡住的题,回炉对应小节。
提示:浏览器发请求 → 服务器返回 HTML → 浏览器解析渲染。不需要讲网络细节(那是阶段三)。
答案:ID 100 + 类 10 + 标签 1 = 111 分。
答案:justify 管主轴、align 管交叉轴。column 时主轴变成垂直,所以 justify-content 变成控制垂直方向。
答案:flex-grow:1 flex-shrink:1 flex-basis:0%,即占满剩余空间。
答案:它们不触发回流重绘,由合成器在 GPU 上处理,性能最好。