进阶工程化 — CodeRoadMap进阶工程化
Sass、CSS Modules 与容器查询。
进阶工程化
这一课你将能做到
- 用 BEM 命名法写出可维护的类名
- 用 CSS 变量做主题切换
- 用 @media 写响应式断点和移动端适配
- 了解浏览器前缀和 PostCSS 在工程中的角色
为什么要学
写到项目第 20 个页面时,你会发现 style="color: red" 和 .red-text 这种命名完全失控。CSS 没有命名空间,全局污染是工程化最大的敌人。BEM、CSS 变量、@media 是现代前端工程的基础装备。
人话:BEM 是类名的「姓名 + 姓 + 身份证号」——.card__title--large 告诉你这是卡片组件(card)里面的标题(title),而且是变体大号(large),看到名字就知道是干嘛的。
先建立图像
<style>
:root {
--color-primary: #0366d6;
--color-bg: #fff;
--color-text: #333;
--space-sm: 8px;
--space-md: 16px;
--radius: 8px;
}
@media (prefers-color-scheme: dark) {
:root {
--color-bg: #1a1a2e;
--color-text: #e0e0e0;
}
}
body {
background: var(--color-bg);
color: var(--color-text);
transition: background 0.3s, color 0.3s;
}
.btn {
padding: var(--space-sm) var(--space-md);
border-radius: var(--radius);
background: var(--color-primary);
color: #fff;
border: none;
cursor: pointer;
}
.btn--outline {
background: transparent;
color: var(--color-primary);
border: 1px solid var(--color-primary);
}
.btn--large {
padding: var(--space-md) 24px;
font-size: 1.125rem;
}
@media (max-width: 768px) {
.btn { padding: 8px 12px; font-size: 0.875rem; }
}
</style>
<button class="btn">主按钮</button>
<button class="btn btn--outline">描边按钮</button>
<button class="btn btn--large">大按钮</button>
跟着做
步骤 1:BEM 命名规范
BEM = Block(块)+ Element(元素)+ Modifier(修饰符)
Block .card ← 一个独立组件
Element .card__title ← 组件内部元素
Modifier .card--dark ← 组件变体
Modifier .card__title--large ← 元素变体
.card { ... } /* 块 */
.card__title { ... } /* 元素 */
.card--dark { ... } /* 块变体 */
.card--dark .card__title { ... } /* 变体下的元素 */
.card__btn--large { ... } /* 元素变体 */
好处:
- 类名全局唯一,不会互相覆盖
- 看到名字就知道结构和用途
- 组件可复用、可预测
步骤 2:CSS 变量(自定义属性)
:root {
--color-primary: #0366d6;
--color-danger: #e74c3c;
--space-unit: 8px;
--font-base: 16px;
}
.btn-primary { background: var(--color-primary); }
.btn-danger { background: var(--color-danger); }
.card { padding: calc(var(--space-unit) * 2); }
CSS 变量可以继承、可重定义、支持 var() 兜底:
.box { color: var(--my-color, #333); } /* 没定义时用 #333 */
.btn { --btn-color: blue; color: var(--btn-color); }
.btn--red { --btn-color: red; } /* 局部覆盖 */
步骤 3:响应式 @media
/* 移动优先:默认是小屏,用大屏断点覆盖 */
.container { padding: 16px; }
@media (min-width: 768px) { /* 平板以上 */
.container { padding: 24px; max-width: 720px; margin: 0 auto; }
}
@media (min-width: 1024px) { /* 桌面 */
.container { max-width: 960px; }
}
@media (min-width: 1280px) { /* 大屏 */
.container { max-width: 1200px; }
}
移动优先(Mobile First)是行业共识:先写手机样式,用 min-width 往上叠加。
手机: < 640px (默认)
平板: >= 768px
桌面: >= 1024px
大屏: >= 1280px
步骤 4:暗色主题
:root {
--bg: #ffffff;
--text: #1a1a1a;
--card-bg: #f5f5f5;
}
@media (prefers-color-scheme: dark) {
:root {
--bg: #0f0f23;
--text: #e0e0e0;
--card-bg: #1a1a2e;
}
}
body { background: var(--bg); color: var(--text); }
.card { background: var(--card-bg); }
[data-theme="dark"] {
--bg: #0f0f23;
--text: #e0e0e0;
--card-bg: #1a1a2e;
}
JS 切换 document.documentElement.setAttribute('data-theme', 'dark') 即可。
步骤 5:浏览器前缀
.example {
-webkit-transition: all 0.3s;
-moz-transition: all 0.3s;
-ms-transition: all 0.3s;
-o-transition: all 0.3s;
transition: all 0.3s;
}
现代浏览器已大幅收敛,但很多项目中仍需处理。不需要手动写——用工具。
步骤 6:PostCSS 简介
PostCSS 是 CSS 的后处理器,用插件在编译期做转换:
→ 写现代 CSS(嵌套、变量、新特性)
→ PostCSS(autoprefixer + postcss-preset-env)
→ 输出兼容老浏览器的 CSS
- autoprefixer:自动加浏览器前缀,根据 browserslist 配置。
- postcss-preset-env:把未来语法编译成现代浏览器能跑的。
- postcss-nested:支持嵌套语法。
/* 嵌套写法(需 PostCSS 处理) */
.card {
padding: 16px;
&__title { font-size: 1.25rem; }
&--dark { background: #333; }
}
步骤 7:实战——一套可维护的按钮系统
<style>
:root {
--btn-primary-bg: #0366d6;
--btn-primary-text: #fff;
--btn-outline-border: #0366d6;
--radius: 6px;
--space-sm: 8px;
--space-md: 16px;
}
.btn {
display: inline-block;
padding: var(--space-sm) var(--space-md);
border-radius: var(--radius);
font-size: 1rem;
cursor: pointer;
text-decoration: none;
border: 1px solid transparent;
transition: opacity 0.2s;
}
.btn:hover { opacity: 0.85; }
.btn--primary {
background: var(--btn-primary-bg);
color: var(--btn-primary-text);
}
.btn--outline {
background: transparent;
color: var(--btn-outline-border);
border-color: var(--btn-outline-border);
}
.btn--large { padding: var(--space-md) 24px; font-size: 1.125rem; }
.btn--small { padding: 4px var(--space-sm); font-size: 0.875rem; }
@media (max-width: 768px) {
.btn { padding: 8px 12px; font-size: 0.875rem; }
}
</style>
<button class="btn btn--primary">主按钮</button>
<button class="btn btn--outline btn--large">大号描边</button>
讲清楚要点
- BEM 的底线:不要写
.card .title,要写 .card__title,确保组件独立。
- CSS 变量不能算颜色以外的数学:
calc() 可以算数值,但 var(--a) + var(--b) 必须包在 calc() 里。
- 移动优先优于桌面优先:符合流量趋势(移动端占多数),且代码更简洁。
- prefers-color-scheme 是暗色主题的最简实现:一行媒体查询搞定系统联动。
- 不要在生产环境手写前缀:autoprefixer 根据项目 browserslist 自动加,比你准。
你可能会懵的地方
-
BEM 名字太长?
习惯就好。好处是明确、可搜索、无冲突。也可用类似 tailwind 的 utility-first 思路。
-
@media 到底该用 max-width 还是 min-width?
移动优先用 min-width(从小到大写);桌面优先用 max-width(从大到小写)。推荐前者。
-
CSS 变量和 SCSS 变量选哪个?
SCSS 变量编译后不存在,不能运行时修改;CSS 变量在浏览器里活到最后。两者可共存:SCSS 管编译期逻辑,CSS 变量管运行时主题。
-
PostCSS 和 SCSS/Less 冲突吗?
不冲突。可以用 SCSS 做嵌套 + 变量,再用 PostCSS 做 autoprefixer + 未来语法。
-
container 查询和 @media 区别?
@media 看视口;@container 看容器尺寸,更适合组件级响应式。新特性,支持还不够全。
错法对照
| 错法 | 问题 | 正解 |
|---|
.card .title 无 BEM | 全局命名冲突 | .card__title |
@media (min-width: 768px) 写一堆覆盖 | 桌面优先反直觉 | 移动优先,用 min-width 往上加 |
var(--x) 拼错不报错 | 静默失效 | 用 var(--x, fallback) |
手写 -webkit- 前缀 | 遗漏且难维护 | 用 autoprefixer |
| 暗色主题改背景不改文字色 | 文字看不见 | 变量成组改:bg + text + border |
!important 覆盖第三方组件 | 恶性循环 | 提高特异性,或用 CSS 变量覆盖 |
卡点急救
- 媒体查询不生效:检查 meta viewport(
<meta name="viewport" content="width=device-width, initial-scale=1">)。
- CSS 变量在某个元素上变 undefined:检查作用域——变量只在声明它的元素及后代有效。
- 暗色模式没反应:检查系统是否开启暗色;检查
prefers-color-scheme 语法是否正确。
面试常问
-
BEM 命名好处?
组件隔离、命名不冲突、结构语义化、团队协作友好。
-
移动优先 vs 桌面优先?
移动优先是先写基础样式,用 min-width 往上叠加。更符合当前流量结构,CSS 更简洁(覆盖少),也更容易适配未知设备。
-
CSS 变量和预处理器的变量有什么区别?
预处理器变量编译后消失,不能在运行时修改;CSS 变量是原生特性,浏览器运行时存在,支持继承和作用域,可以配合 JS 动态修改。
-
PostCSS 做什么?
CSS 后处理工具,用插件做转译:autoprefixer 加前缀、嵌套语法转平铺、新特性兼容转换。
自测三问
.card__btn--disabled 按 BEM 怎么解析?
- 为什么移动优先比桌面优先更好?
- CSS 变量怎么加默认值?
小结一句话
BEM 解决命名冲突,CSS 变量解决主题管理,@media 解决多设备适配,PostCSS 解决浏览器兼容——四件装备加身,CSS 工程化才算入门。
延伸