CSS 实战项目
学了 12 篇基础,是时候把它们串起来做点真东西了。这一章给三个完整、可直接保存成 .html 双击运行的项目——响应式个人主页、动画合集、组件库,每个都从需求出发,给出完整 HTML + CSS 和要点讲解。三个项目分别覆盖 CSS 最核心的三类任务:布局(Flex + Grid + 媒体查询)、动效(transition + keyframes + transform)、工程化(变量 + BEM + 可复用)。建议你把代码复制下来亲手跑一遍,改改颜色、间距、断点,看着效果变化——CSS 是"调"出来的,光看不练永远学不会。
实战 1:响应式个人主页(Flexbox + Grid + 媒体查询)
需求:做一个简洁的个人主页——顶部导航栏 + 主体卡片网格。要求三种屏幕都好看:桌面 3 列、平板 2 列、手机 1 列;手机端导航还要纵向堆叠。一个 HTML 文件搞定,不依赖任何框架。
涉及知识:Flexbox 一维布局(justify-content / align-items)、Grid 二维布局(grid-template-columns + repeat)、媒体查询 @media、box-sizing、视口 meta、盒阴影 box-shadow。
<!-- 响应式个人主页:Flexbox 顶栏 + Grid 卡片网格 + 媒体查询适配 -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>我的个人主页</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: -apple-system, "PingFang SC", sans-serif;
color: #333; background: #f5f7fa; }
/* 顶部导航:Flexbox 横向布局,两端对齐 */
.header {
display: flex;
justify-content: space-between; /* logo 左、导航右 */
align-items: center; /* 垂直居中 */
padding: 16px 32px;
background: #4f8cff;
color: #fff;
}
.nav { display: flex; gap: 24px; }
.nav a { color: #fff; text-decoration: none; font-size: 14px; }
.nav a:hover { text-decoration: underline; }
/* 主体:Grid 自适应卡片网格 */
.main { max-width: 960px; margin: 32px auto; padding: 0 16px; }
.cards {
display: grid;
grid-template-columns: repeat(3, 1fr); /* 默认 3 列等宽 */
gap: 20px;
}
.card {
background: #fff;
border: 1px solid #eee;
border-radius: 10px;
padding: 20px;
box-shadow: 0 2px 8px rgba(0,0,0,.06);
}
.card h3 { margin-bottom: 8px; color: #4f8cff; }
.card p { color: #666; line-height: 1.6; font-size: 14px; }
/* 平板:2 列,顶栏纵向堆叠 */
@media (max-width: 768px) {
.cards { grid-template-columns: repeat(2, 1fr); }
.header { flex-direction: column; gap: 8px; }
}
/* 手机:1 列,导航纵向居中 */
@media (max-width: 480px) {
.cards { grid-template-columns: 1fr; }
.nav { flex-direction: column; align-items: center; gap: 6px; }
}
</style>
</head>
<body>
<header class="header">
<h1>我的主页</h1>
<nav class="nav">
<a href="#">首页</a>
<a href="#">文章</a>
<a href="#">关于</a>
<a href="#">联系</a>
</nav>
</header>
<main class="main">
<section class="cards">
<div class="card"><h3>文章 1</h3><p>Vue 3 Composition API 入门要点。</p></div>
<div class="card"><h3>文章 2</h3><p>CSS Grid 实战布局技巧。</p></div>
<div class="card"><h3>文章 3</h3><p>SQL 索引与慢查询优化。</p></div>
<div class="card"><h3>文章 4</h3><p>Python 自动化脚本分享。</p></div>
<div class="card"><h3>文章 5</h3><p>前端构建工具横向对比。</p></div>
<div class="card"><h3>文章 6</h3><p>设计模式在前端的应用。</p></div>
</section>
</main>
</body>
</html>要点讲解:
box-sizing: border-box:全局重置的标配。让元素的width包含 padding 和 border,算尺寸时不再"超出预期",响应式断点才准。- Flex vs Grid:导航条这种一维横向布局用 Flex;卡片网格这种二维网格用 Grid。两者各有所长,真实项目里常常嵌套使用(本例 header 用 Flex,主体用 Grid)。
repeat(3, 1fr):1fr是"一份剩余空间",repeat(3, 1fr)就是"3 列等宽"。比写33.33%优雅,还会自动留出 gap 不影响列宽。- 移动优先 vs 桌面优先:本例是"桌面优先"——默认 3 列,小屏幕用
@media (max-width: ...)覆盖成 2 列 / 1 列。也可以反过来"移动优先"——默认 1 列,大屏@media (min-width: ...)升级,这种写法对移动设备更友好。 - 视口 meta:
<meta name="viewport" content="width=device-width, initial-scale=1.0">是响应式的"开关",没它手机浏览器会按桌面宽度缩放,媒体查询全部失效。 gap:Flex 和 Grid 都支持gap,比传统的margin优雅得多——不会在容器边缘产生多余外边距。
实战 2:CSS 动画合集(transition + @keyframes + transform)
需求:做一个动画演示页,集中展示五类常用动效——悬停过渡(按钮变色上浮)、循环旋转(加载转圈)、入场淡入(元素从下方淡入)、3D 翻转(卡片翻面)、弹跳循环(球弹跳)。看完这一页,日常 80% 的动画需求都能覆盖。
涉及知识:transition(属性过渡)、@keyframes(关键帧定义)、animation(动画应用)、transform(变换)、opacity、perspective + preserve-3d + backface-visibility(3D 翻转三件套)、infinite / both 等动画选项。
<!-- CSS 动画合集:transition / @keyframes / transform / hover 交互 -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>CSS 动画合集</title>
<style>
body { font-family: sans-serif; padding: 40px; background: #f5f7fa; }
section { margin-bottom: 32px; }
h2 { margin-bottom: 12px; color: #333; }
/* 1. transition:状态过渡(按钮 hover 变色 + 上浮) */
.btn {
padding: 10px 20px;
background: #4f8cff; color: #fff; border: none;
border-radius: 6px; cursor: pointer; font-size: 14px;
transition: background .3s, transform .3s; /* 哪些属性过渡、多久 */
}
.btn:hover {
background: #3a78e0;
transform: translateY(-2px); /* 鼠标移上轻微上浮 */
}
/* 2. @keyframes:循环动画(加载转圈指示器) */
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.loader {
width: 36px; height: 36px;
border: 4px solid #ddd;
border-top-color: #4f8cff; /* 只染顶边 = 旋转的圈 */
border-radius: 50%;
animation: spin 1s linear infinite; /* 1 秒一圈,匀速,无限循环 */
}
/* 3. @keyframes:渐入动画(元素淡入 + 上移) */
@keyframes fadeInUp {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
.fade-in { animation: fadeInUp .8s ease-out both; }
/* both = 动画前后都保持关键帧状态(避免结束后跳回初始) */
/* 4. transform + transition:3D 翻转卡片 */
.flip-card { width: 160px; height: 100px; perspective: 800px; }
.flip-inner {
position: relative; width: 100%; height: 100%;
transition: transform .6s;
transform-style: preserve-3d; /* 子元素保持 3D 位置 */
}
.flip-card:hover .flip-inner { transform: rotateY(180deg); }
.flip-front, .flip-back {
position: absolute; width: 100%; height: 100%;
backface-visibility: hidden; /* 背面不可见,实现翻转效果 */
border-radius: 8px;
display: flex; align-items: center; justify-content: center;
color: #fff; font-weight: bold;
}
.flip-front { background: #4f8cff; }
.flip-back { background: #42b883; transform: rotateY(180deg); }
/* 5. hover 弹跳:用 keyframes 配合 hover 触发 */
@keyframes bounce {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-12px); }
}
.ball {
width: 40px; height: 40px; border-radius: 50%;
background: #e74c3c;
}
.ball:hover { animation: bounce .5s ease infinite; }
</style>
</head>
<body>
<section>
<h2>1. 按钮 hover 过渡</h2>
<button class="btn">悬停看我变色上浮</button>
</section>
<section>
<h2>2. 加载转圈</h2>
<div class="loader"></div>
</section>
<section>
<h2>3. 淡入动画(刷新页面可见)</h2>
<div class="fade-in">我是一段淡入显示的文字</div>
</section>
<section>
<h2>4. 翻转卡片(悬停)</h2>
<div class="flip-card">
<div class="flip-inner">
<div class="flip-front">正面</div>
<div class="flip-back">背面</div>
</div>
</div>
</section>
<section>
<h2>5. 弹跳球(悬停)</h2>
<div class="ball"></div>
</section>
</body>
</html>要点讲解:
transitionvsanimation:transition是"状态 A → B"的过渡,需要触发(hover / class 切换);animation配@keyframes能定义任意多帧、自动循环——不需要触发就能持续运行(如加载转圈)。- 尽量只 transition / transform 这两个:
transform和opacity是合成层属性,GPU 加速,动画丝滑;left/top/width会触发重排,性能差,能不用就不用。 - 转圈的小技巧:
border: 4px solid #ddd; border-top-color: #4f8cff——只给顶边染色,其余透明感的灰,配合border-radius: 50%+rotate就是经典的"spinner"。 both的作用:animation-fill-mode: both让动画在开始前保持from状态、结束后保持to状态。否则淡入动画一加载完会"跳回"初始的透明态。- 3D 翻转三件套:
perspective(父容器设视点)、transform-style: preserve-3d(让子元素保持 3D 空间)、backface-visibility: hidden(背面不可见)——三个属性缺一不可,少一个翻转就废。 - 动效克制:动画的目的是引导注意力或给反馈,不是炫技。一个页面同时跑 5 个动画会很廉价——生产环境请克制,通常 0.2~0.4 秒的微妙过渡就够了。
实战 3:组件库实战(CSS 变量 + BEM + 可复用)
需求:用纯 CSS 实现一套迷你组件库——按钮(多种颜色 + 尺寸)、卡片(图片 + 正文 + 操作栏)、导航(品牌名 + 菜单 + 选中态)。所有配色、间距、圆角都抽成CSS 变量(design token),命名一律用 BEM 规范,改一处全站生效。
涉及知识::root + --var 定义设计 token、var(--name) 引用、BEM(block__element--modifier)命名、修饰符组合复用、transition 配合 token、组件结构语义化。
<!-- 组件库实战:按钮/卡片/导航,用 CSS 变量 + BEM 命名 -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>CSS 组件库</title>
<style>
/* === 设计 token:全站配色/间距/圆角的"单一真相源" === */
/* 改这里一处,全站所有组件自动跟着变 */
:root {
--color-primary: #4f8cff;
--color-primary-dark: #3a78e0;
--color-danger: #e74c3c;
--color-success: #42b883;
--color-text: #333;
--color-text-light: #666;
--color-bg: #fff;
--color-border: #e5e7eb;
--radius: 8px;
--space: 16px;
--shadow: 0 2px 8px rgba(0,0,0,.08);
--transition: .2s ease;
}
/* === BEM 命名:block__element--modifier === */
/* .btn 块(block):独立的组件 */
/* .btn__icon 元素(element):块内部的子部分 */
/* .btn--danger 修饰符(modifier):块的状态/变体 */
/* ---------- 按钮组件 ---------- */
.btn {
display: inline-flex; align-items: center; gap: 6px;
padding: 10px 20px;
background: var(--color-primary);
color: #fff; border: none; border-radius: var(--radius);
font-size: 14px; cursor: pointer;
transition: background var(--transition), transform var(--transition);
}
.btn:hover { background: var(--color-primary-dark); transform: translateY(-1px); }
.btn:active { transform: translateY(0); } /* 点击时下沉 */
/* 颜色修饰符:不同语义 */
.btn--danger { background: var(--color-danger); }
.btn--ghost { background: transparent;
color: var(--color-primary);
border: 1px solid var(--color-primary); }
/* 尺寸修饰符 */
.btn--small { padding: 6px 12px; font-size: 12px; }
.btn--large { padding: 14px 28px; font-size: 16px; }
/* ---------- 卡片组件 ---------- */
.card {
background: var(--color-bg);
border: 1px solid var(--color-border);
border-radius: var(--radius);
box-shadow: var(--shadow);
overflow: hidden;
max-width: 280px;
}
.card__image {
width: 100%; height: 140px;
background: #dfe6f0; /* 真实项目用 <img src> */
display: flex; align-items: center; justify-content: center;
color: #999; font-size: 14px;
}
.card__body { padding: var(--space); }
.card__title { font-size: 16px; margin: 0 0 8px; color: var(--color-text); }
.card__text { font-size: 14px; color: var(--color-text-light);
line-height: 1.6; margin: 0; }
.card__footer { padding: 0 var(--space) var(--space);
display: flex; gap: 8px; }
/* ---------- 导航组件 ---------- */
.nav {
display: flex; align-items: center; gap: var(--space);
background: var(--color-bg);
border-bottom: 1px solid var(--color-border);
padding: 0 var(--space);
height: 56px;
}
.nav__brand { font-weight: bold; font-size: 18px;
color: var(--color-primary); }
.nav__list { display: flex; gap: var(--space);
list-style: none; margin: 0; padding: 0; }
.nav__link { color: var(--color-text); text-decoration: none;
font-size: 14px;
transition: color var(--transition); }
.nav__link:hover { color: var(--color-primary); }
.nav__link--active { color: var(--color-primary); font-weight: bold; }
</style>
</head>
<body>
<!-- 导航 -->
<nav class="nav">
<span class="nav__brand">MyUI</span>
<ul class="nav__list">
<li><a class="nav__link nav__link--active" href="#">首页</a></li>
<li><a class="nav__link" href="#">产品</a></li>
<li><a class="nav__link" href="#">文档</a></li>
</ul>
</nav>
<!-- 按钮:各种修饰符组合 -->
<div style="padding:24px;display:flex;gap:12px;flex-wrap:wrap;align-items:center">
<button class="btn">默认按钮</button>
<button class="btn btn--danger">危险按钮</button>
<button class="btn btn--ghost">幽灵按钮</button>
<button class="btn btn--small">小按钮</button>
<button class="btn btn--large">大按钮</button>
</div>
<!-- 卡片 -->
<div style="padding:0 24px 24px">
<div class="card">
<div class="card__image">图片占位</div>
<div class="card__body">
<h3 class="card__title">组件化思想</h3>
<p class="card__text">把界面拆成可复用的独立单元,每个组件管自己的结构和样式,通过 BEM 命名避免冲突。</p>
</div>
<div class="card__footer">
<button class="btn btn--small">详情</button>
<button class="btn btn--small btn--ghost">收藏</button>
</div>
</div>
</div>
</body>
</html>要点讲解:
- 设计 token:把所有可调参数(颜色、间距、圆角、阴影、过渡时长)集中定义在
:root。改品牌色只需改--color-primary一处,全站按钮 / 链接 / 选中态全部跟着变——这是主题化的基础。 - BEM 三段式:
block(独立组件,如btn)、block__element(组件内部子部分,如card__title)、block--modifier(组件变体,如btn--danger)。命名一眼看出"属于谁、什么角色",彻底告别选择器嵌套地狱。 - 修饰符组合:
class="btn btn--small btn--ghost"是"基础类 + 尺寸修饰符 + 颜色修饰符"的组合。修饰符只覆盖差异部分(尺寸或颜色),其余从基类继承——一个按钮可以同时是"小号 + 幽灵 + 危险"三种变体,代码极简。 - 避免选择器嵌套:BEM 用扁平的类名代替
.card .card-body .title这种深层选择器,优先级低、好覆盖、好复用,也避免和第三方样式打架。 var()带回退:var(--space, 16px)第二个参数是回退值——变量没定义时用它,做组件库时给使用者留出兜底。- 工程意义:这三件套(变量 + BEM + 可复用)是 Element Plus / Ant Design 等大组件库的核心思路。即便你日后用 CSS-in-JS 或 Tailwind,这些底层思维依然适用——它们只是把这个模式用不同语法实现。
小结
三个项目对应 CSS 三大核心场景:布局(响应式主页)、动效(动画合集)、工程化(组件库)。它们都用到前面学的基础——盒模型、选择器、Flex / Grid、定位、transition / animation、变量——只是组合起来做出真实可用的东西。建议你至少把其中一个改造成自己的版本:给主页加暗色模式、给动画加滚动触发、给组件库加更多组件(输入框、模态框、表格)——做过一遍比读十遍更扎实。
恭喜!到这里 CSS 系列就真正完结了。接下来可以深入——CSS 架构(Tailwind / CSS Modules / CSS-in-JS)、动画库(GSAP / Framer Motion)、设计系统(读懂 Element Plus 源码)、CSS Houdini / 容器查询——方向很多,但底层 CSS 你已经具备。
← 上一篇 CSS 变量
返回 CSS 教程目录