综合练习:静态课程页
仿制课程目录静态页,无 JS 框架。
HTML/CSS 综合实战
这一课你将能做到
- 从零构建一个完整的 CodeForge 课程详情页,包含语义化结构、响应式导航、课程卡片网格和登录表单
- 综合运用 Flexbox、Grid、自定义属性、媒体查询和容器查询,实现一个无需 JavaScript 即可自适应不同屏幕的页面
- 理解「结构 → 样式 → 响应式 → 可访问性」的完整开发流程
为什么要学
零散的知识点只有在实战中才能连成体系。你可能已经知道 <article> 的语义、flex: 1 的含义、grid-template-areas 的写法,但当你面对一个真实的设计稿——顶部导航、hero 区域、课程卡片网格、侧边栏推荐、页脚版权——你需要的不是单点知识,而是如何把它们组织成一个自洽、可维护的整体。
本课不引入任何 JavaScript 框架,只用 HTML 和 CSS 构建一个完整的课程详情页。通过这个实战,你会体会到:为什么语义化标签让结构更清晰,为什么 CSS 变量让主题切换更容易,为什么移动优先的媒体查询比桌面优先更优雅。
先建立图像
页面整体结构:
┌──────────────────────────────────────────┐
│ [Logo] [课程] [路线图] [社区] [登录] │ ← header (Flexbox)
├──────────────────────────────────────────┤
│ │
│ HTML/CSS 基础精讲 │ ← hero section
│ 从零构建现代网页的完整入门课程 │
│ │
├──────────────────┬───────────────────────┤
│ │ │
│ [课程卡片] │ [讲师信息] │ ← main (Grid: 2列)
│ [课程卡片] │ [相关推荐] │
│ │ │
├──────────────────┴───────────────────────┤
│ © 2026 CodeForge │ ← footer
└──────────────────────────────────────────┘
移动端(< 768px):单列,导航折叠为汉堡菜单
平板(768px+):双列,卡片 2 列
桌面(1024px+):双列,卡片 3 列
跟着做
步骤 1:HTML 语义化骨架
创建 practice.html:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>HTML/CSS 基础精讲 | CodeForge</title>
<link rel="stylesheet" href="practice.css">
</head>
<body>
<header>
<nav aria-label="主导航">
<a href="/" class="logo">CodeForge</a>
<ul class="nav-links">
<li><a href="#courses">课程</a></li>
<li><a href="#roadmaps">路线图</a></li>
<li><a href="#community">社区</a></li>
</ul>
<div class="nav-actions">
<a href="#login" class="btn btn-ghost">登录</a>
<a href="#register" class="btn btn-primary">注册</a>
</div>
<button class="menu-toggle" aria-label="打开菜单" aria-expanded="false">☰</button>
</nav>
</header>
<main>
<section class="hero" aria-labelledby="course-title">
<div class="container">
<span class="badge">前端基础</span>
<h1 id="course-title">HTML/CSS 基础精讲</h1>
<p class="hero-desc">从零开始构建现代网页。掌握语义化标签、CSS 盒模型、Flexbox、Grid 布局与响应式设计。</p>
<div class="hero-meta"><span>36 课时</span><span>·</span><span>12 小时视频</span><span>·</span><span>配套练习</span></div>
</div>
</section>
<section class="content" aria-labelledby="content-title">
<div class="container grid-layout">
<div class="main-content">
<h2 id="content-title" class="section-title">课程大纲</h2>
<ol class="syllabus">
<li><h3>网页如何工作</h3><p>浏览器渲染原理、HTML/CSS/JS 职责分离、开发者工具。</p></li>
<li><h3>HTML 文档结构与内容标签</h3><p>标准骨架、语义化标签、列表、链接、图片与表格。</p></li>
<li><h3>HTML 表单与语义化</h3><p>登录表单构建、ARIA 可访问性、表单验证基础。</p></li>
<li><h3>CSS 基础与盒模型</h3><p>选择器、层叠与继承、特异性、盒模型、自定义属性。</p></li>
<li><h3>Flexbox 与 Grid 布局</h3><p>一维弹性布局、二维网格布局、响应式卡片网格。</p></li>
<li><h3>响应式设计与现代 CSS</h3><p>媒体查询、容器查询、流体排版、深色模式适配。</p></li>
</ol>
</div>
<aside class="sidebar" aria-label="课程信息">
<div class="sidebar-card">
<h2 class="sidebar-title">讲师介绍</h2>
<div class="instructor">
<div class="avatar" role="img" aria-label="讲师头像">CF</div>
<div>
<p class="instructor-name">CodeForge 团队</p>
<p class="instructor-bio">专注于前端基础教育的工程师团队,已有超过 10 万名学员。</p>
</div>
</div>
</div>
<div class="sidebar-card">
<h2 class="sidebar-title">相关课程</h2>
<ul class="related-list">
<li><a href="#">JavaScript 核心原理</a></li>
<li><a href="#">React 开发实战</a></li>
<li><a href="#">TypeScript 从入门到进阶</a></li>
</ul>
</div>
</aside>
</div>
</section>
</main>
<footer><div class="container"><p>© 2026 CodeForge 学习平台. 保留所有权利。</p></div></footer>
</body>
</html>
语义结构:<header> 包导航,<main> 包主内容,<section> 带标题,<aside> 包辅助信息,<footer> 包版权。每个区域都有 aria-label 或 aria-labelledby 关联。
步骤 2:核心 CSS 片段
创建 practice.css,核心样式如下(完整代码约 200 行):
:root {
--primary: #2563eb; --primary-hover: #1d4ed8; --accent: #7c3aed;
--bg: #f8fafc; --surface: #ffffff; --text: #0f172a; --text-muted: #64748b;
--border: #e2e8f0; --radius: 12px;
}
@media (prefers-color-scheme: dark) {
:root { --bg: #0f172a; --surface: #1e293b; --text: #f8fafc; --text-muted: #94a3b8; --border: #334155; }
}
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; background: var(--bg); color: var(--text); line-height: 1.6; }
.container { max-width: 1200px; margin-inline: auto; padding-inline: clamp(1rem, 0.5rem + 2vw, 2rem); }
header { background: var(--surface); border-bottom: 1px solid var(--border); position: sticky; top: 0; z-index: 10; }
nav { display: flex; align-items: center; justify-content: space-between; padding-block: 0.75rem; }
.logo { font-weight: 700; font-size: 1.25rem; color: var(--primary); text-decoration: none; }
.nav-links { display: none; gap: 1.5rem; list-style: none; }
.nav-links a { color: var(--text); text-decoration: none; font-weight: 500; font-size: 0.9375rem; }
.nav-actions { display: none; gap: 0.75rem; }
.menu-toggle { background: none; border: none; font-size: 1.5rem; cursor: pointer; color: var(--text); }
.btn { display: inline-flex; align-items: center; justify-content: center; padding: 0.5rem 1rem; border-radius: 8px; font-size: 0.9375rem; font-weight: 500; text-decoration: none; cursor: pointer; transition: background 0.2s; }
.btn-primary { background: var(--primary); color: white; border: none; }
.btn-ghost { background: transparent; color: var(--text); border: 1px solid var(--border); }
.hero { padding-block: clamp(3rem, 2rem + 4vw, 5rem); background: linear-gradient(135deg, #eff6ff, #f5f3ff); text-align: center; }
@media (prefers-color-scheme: dark) { .hero { background: linear-gradient(135deg, #1e293b, #0f172a); } }
.badge { display: inline-block; padding: 0.25rem 0.75rem; background: rgba(37, 99, 235, 0.1); color: var(--primary); font-size: 0.875rem; font-weight: 500; border-radius: 999px; margin-bottom: 1rem; }
.hero h1 { font-size: clamp(1.75rem, 1.2rem + 2.5vw, 2.75rem); line-height: 1.2; margin-bottom: 1rem; }
.hero-desc { font-size: clamp(1rem, 0.9rem + 0.5vw, 1.125rem); color: var(--text-muted); max-width: 640px; margin-inline: auto; margin-bottom: 1.5rem; }
.hero-meta { display: flex; justify-content: center; gap: 0.5rem; font-size: 0.875rem; color: var(--text-muted); }
.content { padding-block: 3rem; }
.grid-layout { display: grid; gap: 2rem; }
.section-title { font-size: 1.5rem; margin-bottom: 1.5rem; }
.syllabus { list-style: none; counter-reset: lesson; }
.syllabus li { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: 1.25rem; margin-bottom: 1rem; display: flex; gap: 1rem; align-items: flex-start; }
.syllabus li::before { counter-increment: lesson; content: counter(lesson, decimal-leading-zero); font-size: 1.5rem; font-weight: 700; color: var(--primary); line-height: 1; flex-shrink: 0; min-width: 2.5rem; }
.syllabus h3 { font-size: 1.125rem; margin-bottom: 0.25rem; }
.syllabus p { color: var(--text-muted); font-size: 0.9375rem; }
.sidebar { display: flex; flex-direction: column; gap: 1.5rem; }
.sidebar-card { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: 1.5rem; }
.sidebar-title { font-size: 1.125rem; margin-bottom: 1rem; }
.instructor { display: flex; gap: 1rem; align-items: flex-start; }
.avatar { width: 48px; height: 48px; border-radius: 50%; background: linear-gradient(135deg, var(--primary), var(--accent)); color: white; display: grid; place-items: center; font-weight: 600; font-size: 0.875rem; flex-shrink: 0; }
.instructor-name { font-weight: 600; margin-bottom: 0.25rem; }
.instructor-bio { font-size: 0.875rem; color: var(--text-muted); }
.related-list { list-style: none; }
.related-list li { padding-block: 0.5rem; border-bottom: 1px solid var(--border); }
.related-list li:last-child { border-bottom: none; }
.related-list a { color: var(--text); text-decoration: none; font-size: 0.9375rem; }
footer { background: var(--surface); border-top: 1px solid var(--border); padding-block: 2rem; text-align: center; font-size: 0.875rem; color: var(--text-muted); }
@media (min-width: 768px) {
.nav-links { display: flex; }
.nav-actions { display: flex; }
.menu-toggle { display: none; }
.grid-layout { grid-template-columns: 1fr 280px; }
}
@media (min-width: 1024px) {
.grid-layout { grid-template-columns: 1fr 320px; gap: 3rem; }
}
步骤 3:测试与验证
- 在 Chrome 中打开
practice.html,按 F12 切换到移动设备模拟,测试 375px、768px、1024px、1440px 下的显示效果
- 检查标题层级:页面应只有一个
<h1>,且 <h2>、<h3> 层级连续
- 只用键盘操作:按
Tab 依次聚焦 logo、导航链接、按钮、大纲项,确认焦点可见且顺序合理
- 切换系统深色模式,观察页面是否自动适配
- 运行 Lighthouse,可访问性分数应 ≥ 90
讲清楚要点
-
结构与样式分离的边界:HTML 负责语义和层级,CSS 负责视觉和布局,两者不要互相侵入。不要在 HTML 中写 style="color: red",也不要在 CSS 选择器中依赖具体的 HTML 结构。
-
CSS 变量是主题系统的基石:本页所有颜色都通过变量引用。切换深色模式只需覆盖 :root 中的 6 个变量,而不是全局搜索替换几十个颜色值。这是设计令牌(Design Tokens)的基础概念。
-
移动优先的媒体查询策略:基础样式针对小屏,断点用 min-width 逐步增强。这样小屏设备不会加载冗余的桌面样式,且代码逻辑自然流畅。
-
语义化是 SEO 和无障碍的基础:<header>、<nav>、<main>、<section>、<aside>、<footer> 让搜索引擎快速识别页面结构,让屏幕阅读器用户通过地标(landmark)快速跳转。
-
流体排版减少断点维护:标题和描述使用 clamp(),在 320px 到 1920px 之间平滑缩放,无需为每个断点单独写 font-size。这显著减少了代码量和维护成本。
-
容器查询的实战价值:本页侧边栏卡片内部没有使用容器查询,但在更复杂的组件库中,卡片放在窄侧边栏和宽主内容区需要不同布局——这正是容器查询解决的核心问题。
你可能会懵的地方
-
「为什么要把 CSS 拆成单独文件,而不是写 <style>?」
单文件 Demo 可以写 <style>,但生产环境中外部 CSS 可被浏览器缓存,多页面共享同一份样式,减少重复下载。
-
「选择器很多,怎么管理不混乱?」
按页面区域组织(导航、Hero、内容、侧边栏、页脚、响应式),每个区域内部按「布局 → 元素 → 状态(hover/focus)」排序。更大项目应使用 BEM、SMACSS 或 CSS-in-JS 等命名规范。
-
「为什么不用 Tailwind 写?不是更方便吗?」
Tailwind 建立在原生 CSS 之上。不理解 Flexbox、Grid、层叠和自定义属性,用 Tailwind 也只是「知其然不知其所以然」。本课程故意用原生 CSS 训练底层能力,下一课会介绍 Tailwind。
-
「这个页面没有 JavaScript,菜单怎么展开?」
纯 HTML/CSS 可以用 :target 伪类或 checkbox hack 实现无 JS 菜单,但可访问性不佳。生产环境中,汉堡菜单需要 JavaScript 切换 aria-expanded 和 hidden 属性,并管理焦点。本课为聚焦 CSS 布局,菜单按钮仅作视觉占位。
-
「如何确定断点值?」
不应基于具体设备,而应基于内容。当课程卡片在某一宽度下变得过于拥挤时,那个宽度就是断点。常用参考值 640px、768px、1024px、1280px、1440px 来自内容而非设备型号。
错法对照
| 错法 | 问题 | 正解 |
|---|
HTML 中大量写 style="..." | 难以维护,权重过高,无法缓存复用 | 用外部 CSS 和类选择器 |
用 !important 覆盖框架样式 | 层叠秩序破坏,后续调试困难 | 提高选择器特异性或调整加载顺序 |
| 断点基于 iPhone/Android 具体型号 | 设备繁多,无法覆盖 | 基于内容何时崩坏设断点 |
| 忽略键盘焦点样式 | 键盘用户无法知道当前焦点在哪 | 为 :focus-visible 提供可见轮廓 |
| 深色模式只改背景不改文字 | 对比度不足,无法阅读 | 用变量系统统一改背景、文字、边框 |
图片没有 width 和 height | 加载时布局偏移(CLS) | 始终提供图片尺寸,或用 CSS aspect-ratio |
卡点急救
-
页面在手机上看被缩小:检查 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 是否存在。没有它所有媒体查询都无效。
-
CSS 文件加载了但样式不对:检查 link 标签的 href 路径是否正确,文件编码是否为 UTF-8,在 Network 面板确认 CSS 文件返回 200。
-
Lighthouse 可访问性分数低:常见原因包括图片缺少 alt、颜色对比度不足、标题层级跳跃、表单字段没有 label。逐项修复后再运行审计。
面试常问
-
「描述一下你构建一个响应式页面的完整流程。」
一、分析设计稿,识别页面区域和组件。二、写语义化 HTML 骨架,使用正确的标题层级和 ARIA 属性。三、设置 CSS 变量系统。四、写移动优先的基础样式。五、用 clamp() 实现流体排版和弹性间距。六、用 min-width 媒体查询逐步增强布局。七、测试键盘可访问性、屏幕阅读器播报和 Lighthouse 分数。八、添加深色模式支持和打印样式。
-
「如何确保页面在不同设备上体验一致?」
一、使用响应式图片(srcset + sizes)。二、使用 clamp() 和容器查询让组件自适应容器。三、在真实设备上测试。四、检查触摸目标尺寸(至少 44×44 CSS 像素)。五、确保字体在各类屏幕上清晰可读(系统字体栈、适当行高)。
-
「CSS 变量和预处理器变量(Sass)如何选择?」
CSS 自定义属性运行时可用,支持作用域和继承,可被 JavaScript 读写,适合主题切换。Sass 变量编译时静态替换,适合管理编译期常量(如断点值)。两者可共存:Sass 管理构建期配置,CSS 变量管理运行时主题。
自测三问
- 如果你要把这个页面的课程大纲从有序列表改成卡片网格,最少需要修改哪些 CSS?如何确保修改不破坏现有语义结构?
- 这个页面如何同时支持「系统深色模式」和「手动切换深色模式」?描述实现思路。
- 为什么本页面的导航栏在 768px 以下隐藏链接、显示汉堡按钮?如果要在不增加断点的情况下让导航更早换行,可以怎么做?
小结一句话
一个专业的页面是「语义 HTML + 现代 CSS + 响应式策略 + 可访问性」的有机整体;本课的综合实战把这些知识点串联成一条完整的开发流水线,是你从「会写代码」到「会写产品」的关键跨越。
延伸