
CSS 很奇妙:在 Web 开发里,它既常被认为是最容易上手的语言之一,也被认为是最难学精的语言之一。入门确实不难——给元素写上样式属性与取值,差不多就能开工;可一旦项目变大,想把 CSS 组织得「有意义」就会变得纠缠而复杂。你改一行样式想动某一页上的某个元素,却往往在别的页面上牵出意外变化。
为了驾驭 CSS 自带的复杂度,业界积累过各式各样的「最佳实践」。麻烦在于:究竟哪套才算最好,从来没有强共识,不少做法彼此之间还看似完全矛盾。若是第一次系统学 CSS,很容易晕头转向。
本文想补上一段历史脉络:CSS 的方法论与工具链是如何一路演进到 2018 年前后的样貌。弄清来龙去脉之后,再遇到各种方案,你会更容易判断各自解决什么问题、怎么为己所用。下面开始。
更新:我把本文做成了视频课,材料讲得更细,见:
https://firstclass.actualize.co/p/modern-css-explained-for-dinosaurs
使用 CSS 进行基本样式设置
先从一个最简站点开始:index.html 外链同目录的 index.css。
<!DOCTYPE html><html lang="en"> <head> <meta charset="UTF-8" /> <title>Modern CSS</title> <link rel="stylesheet" href="index.css" /> </head> <body> <header>This is the header.</header> <main> <h1>This is the main content.</h1> <p>...</p> </main> <nav> <h4>This is the navigation section.</h4> <p>...</p> </nav> <aside> <h4>This is an aside section.</h4> <p>...</p> </aside> <footer>This is the footer.</footer> </body></html>HTML 里还没加 class/id,只用 语义化区块标签。不上样式时,页面大概这样(占位文案):

能读,但不好看。在 index.css 里加一点基础排版:
/* BASIC TYPOGRAPHY *//* from https://github.com/oxalorg/sakura */
html { font-size: 62.5%; font-family: serif;}
body { font-size: 1.8rem; line-height: 1.618; max-width: 38em; margin: auto; color: #4a4a4a; background-color: #f9f9f9; padding: 13px;}
@media (max-width: 684px) { body { font-size: 1.53rem; }}
@media (max-width: 382px) { body { font-size: 1.35rem; }}
h1,h2,h3,h4,h5,h6 { line-height: 1.1; font-family: Verdana, Geneva, sans-serif; font-weight: 700; overflow-wrap: break-word; word-wrap: break-word; -ms-word-break: break-all; word-break: break-word; -ms-hyphens: auto; -moz-hyphens: auto; -webkit-hyphens: auto; hyphens: auto;}
h1 { font-size: 2.35em;}
h2 { font-size: 2em;}
h3 { font-size: 1.75em;}
h4 { font-size: 1.5em;}
h5 { font-size: 1.25em;}
h6 { font-size: 1em;}这段 CSS 多半在调字号、行高、字体和少量颜色、居中,本身不算难;好数值要靠设计直觉(示例来自 sakura.css)。效果如下:

和素颜页面对比,差别很大——这就是 CSS 的卖点:不用写程序,给文档「加一层」样式即可。但只做排版配色时简单,一旦要做复杂布局,问题就会棘手起来(下面会讲)。
使用 CSS 进行布局
在 CSS 普及之前(1990 年代),页面布局手段不多。HTML 本来是为「文档流」设计的,不是为侧栏、多栏站型准备的。早年流行用 <table> 排版:整站塞进一张表,用行列对齐——能用,但内容和表现绑死,改布局往往要大改 HTML。
CSS 出现后,业界力推 HTML 管内容、CSS 管呈现;布局从表格迁到样式表。但和 HTML 一样,CSS 最初也不是为「整页版式」设计的,早期想分得漂亮,其实很别扭。
仍用上面的例子:正式写布局前,先清掉默认 margin/padding(否则算宽度会飘),再给各区块涂不同底色——不是为了好看,而是试布局时一眼分辨区域。
/* RESET LAYOUT AND ADD COLORS */
body { margin: 0; padding: 0; max-width: inherit; background: #fff; color: #4a4a4a;}
header,footer { font-size: large; text-align: center; padding: 0.3em 0; background-color: #4a4a4a; color: #f9f9f9;}
nav { background: #eee;}
main { background: #f9f9f9;}
aside { background: #eee;}现在网站暂时看起来像:

下面按时间顺序看三种典型布局:float → flexbox → grid,先从 float 说起。
基于 float 的布局
float 最早是给文字绕图用的(报纸排版那种)。2000 年代初,有人发现不仅能浮图片,还能浮任意块,靠多层 float 凑出「列」的错觉——但 float 本不是为整页栅格设计的,想稳定复现很难。
2006 年 A List Apart 上的 Holy Grail 把「头 + 三栏 + 尾」这种今天听起来很普通的版式,写成一篇长文——可见当年用纯 CSS 做稳定布局有多痛苦。
下面这段 float 布局就沿用了那篇文章里的思路:
/* FLOAT-BASED LAYOUT */
body { padding-left: 200px; padding-right: 190px; min-width: 240px;}
header,footer { margin-left: -200px; margin-right: -190px;}
main,nav,aside { position: relative; float: left;}
main { padding: 0 20px; width: 100%;}
nav { width: 180px; padding: 0 10px; right: 240px; margin-left: -100%;}
aside { width: 130px; padding: 0 10px; margin-right: -100%;}
footer { clear: both;}
* html nav { left: 150px;}看看这份 CSS,你会发现要让它跑起来需要不少技巧(负边距, clear: both 属性,硬编码宽度计算等)——这篇 文章 很好地详细解释了每个原因。结果如下所示:

版式能跑,但三栏等高、撑满视口高度,float 时代很难优雅解决——float 只管左右靠,无法让各栏随内容「对齐高度」。多年后才有更顺手的工具:Flexbox。
基于弹性框的布局
Flexbox 约 2009 年提出,到 2015 年前后才真正普及。它擅长在单行或单列里分配空间,比 float 更适合当「布局主力」——折腾 float 十年后,开发者终于能用更直白的方式做对齐与伸缩。
下面示例沿用 Solved by Flexbox 里的圣杯写法。注意:三栏外需要包一层 .container,HTML 要动一下:
<!DOCTYPE html><html lang="en"> <head> <meta charset="UTF-8" /> <title>Modern CSS</title> <link rel="stylesheet" href="index.css" /> </head> <body> <header>This is the header.</header> <div class="container"> <main> <h1>This is the main content.</h1> <p>...</p> </main> <nav> <h4>This is the navigation section.</h4> <p>...</p> </nav> <aside> <h4>This is an aside section.</h4> <p>...</p> </aside> </div> <footer>This is the footer.</footer> </body></html>对应 CSS:
/* FLEXBOX-BASED LAYOUT */
body { min-height: 100vh; display: flex; flex-direction: column;}
.container { display: flex; flex: 1;}
main { flex: 1; padding: 0 20px;}
nav { flex: 0 0 180px; padding: 0 10px; order: -1;}
aside { flex: 0 0 130px; padding: 0 10px;}相比 float,少了一堆负边距之类的 hack,可读性也好一截。效果:

三栏等高、主体区域能吃满视口高度,比 float 时代舒服多了。代价也有:老浏览器永远不会支持 flex(只能靠放弃兼容或 polyfill);厂商近年加速弃用旧 IE,倒是让这条没那么痛。另一点:为了 flex,你往往得多包一层 .container——理想情况当然是不改 HTML 只改 CSS。
还有人吐槽:flex 的声明式程度仍不够——读一坨 flex-grow/shrink/basis 未必立刻在脑子里画出版图,调参仍常靠试。
更要记住:flex 设计给「一维」排布(一行或一列里的分配);二维整页栅格另有专门规范——CSS Grid。
基于网格的布局
Grid 约 2011 年提出,浏览器落地比 flex 还慢一截;到 2018 年初,支持面已经比一两年前好太多。
下面这段跟 CSS-Tricks:同一版式多种写法 里的第一种接近。这次不必再为 flex 加 .container,可以回到最初那份 HTML,只改样式:
/* GRID-BASED LAYOUT */
body { display: grid; min-height: 100vh; grid-template-columns: 200px 1fr 150px; grid-template-rows: min-content 1fr min-content;}
header { grid-row: 1; grid-column: 1 / 4;}
nav { grid-row: 2; grid-column: 1 / 2; padding: 0 10px;}
main { grid-row: 2; grid-column: 2 / 3; padding: 0 20px;}
aside { grid-row: 2; grid-column: 3 / 4; padding: 0 10px;}
footer { grid-row: 3; grid-column: 1 / 4;}视觉上和 flex 版接近,但 Grid 把「行/列与格子」说得更直白:列宽、行高在 grid-template-* 里定,子项用 grid-row / grid-column 落位。
grid-column: 1 / 4 这类写法,数字是网格线编号:三列会有 1~4 四条竖线(见下图就直观了)。

首列占线 1–2,次列 2–3,末列 3–4;header 写 1 / 4 是横跨三列。熟手之后,Grid 往往是**最像「在描述版式」**的写法。
浏览器支持到 2018 已明显改善。Grid 是 CSS 第一次真正为二维布局而生的工具——重要性怎么说都不过分。以前工具脆、全靠 hack,设计师做大胆版式心里没底;Grid 普及后,创意空间会大很多。

将 CSS 预处理器用于新语法
样式与布局说完,进入工具层:先讲 CSS 预处理器——用另一种语法写样式,再编译成浏览器认识的 CSS。在浏览器追新极慢的年代,这能显著改善开发体验。
鼻祖级的是 2006 年的 Sass:缩进语法、可省大括号分号,并带来 变量、函数、运算 等当时原生 CSS 没有的能力。仍用前面配色举例,Sass 可以写成:
$dark-color: #4a4a4a$light-color: #f9f9f9$side-color: #eee
body color: $dark-color
header, footer background-color: $dark-color color: $light-color
main background: $light-color
nav, aside background: $side-color$ 变量、缩进块,读起来省字;变量在当年算「大招」,换主题色、统一间距会轻松很多。用经典 Sass 需 装 Ruby、再装 Sass gem,按文档把 .sass 编译成 .css。例如:
sass --watch index.sass index.css--watch 会在保存时自动重编。这就是构建步骤——2006 年对「只会写静态页」的前端来说门槛不低;对习惯 Ruby 的人则很轻松。
2009 年 Less 出现,早期同样走 Ruby,能力与 Sass 接近,但语法刻意贴近 CSS:任意合法 CSS 都是合法 Less。同一例子写成 Less:
@dark-color: #4a4a4a;@light-color: #f9f9f9;@side-color: #eee;
body { color: @dark-color;}
header,footer { background-color: @dark-color; color: @light-color;}
main { background: @light-color;}
nav,aside { background: @side-color;}变量用 @ 而非 $,大括号分号都还在,不如缩进 Sass「好看」,但迁移成本低。2012 年 Less 改用 JavaScript(常见是 Node.js)实现编译,比纯 Ruby 路线更快,也更好融入当时开始流行的 Node 前端工具链。
lessc index.less index.csslessc 自带不像 sass --watch 那么省心,想监听文件往往要再接 watch 工具,复杂度又高一截。
Less 走红后,Sass 在 2010 年推出与 CSS 更像的 SCSS 语法,并开源 LibSass(C/C++ 实现),速度更快、语言绑定更多。
另一条线是 2010 年的 Stylus,Node 实现,语法可以更「极简」。实务里 Sass / Less / Stylus 讨论最多,能力大体同档,选哪个往往看团队习惯。
后来浏览器原生支持 变量、calc() 等,有人说预处理器「没那么必要了」;同时 CSS 后处理(PostCSS 等)又提供了另一条路——能不能取代预处理,至今仍有争论。下面看后处理。
将 CSS 后处理器用于变革性功能
后处理器同样用 JavaScript 读入样式,再输出仍是合法 CSS——解决的问题类似预处理,但入口语法就是普通 CSS:不靠 $、嵌套规则等特殊语法标记「要变哪」,而是对标准 CSS 做变换。
看标题样式里这段(带厂商前缀):
h1,h2,h3,h4,h5,h6 { -ms-hyphens: auto; -moz-hyphens: auto; -webkit-hyphens: auto; hyphens: auto;}这些带 -ms- / -moz- / -webkit- 的,就是厂商前缀(vendor prefix):浏览器在实验特性阶段用来「试跑」新属性,等稳定后再收敛到无前缀的标准写法。手打一长串前缀很烦,于是有人用 mixin 包一层——例如 SCSS:
@mixin hyphens($value) { -ms-hyphens: $value; -moz-hyphens: $value; -webkit-hyphens: $value; hyphens: $value;}h1,h2,h3,h4,h5,h6 { @include hyphens(auto);}Sass mixin 能复用,但每个要前缀的属性你都得先写一版 @mixin,浏览器兼容性变了还要手动删旧前缀。
更省心的做法是:只写标准属性,交给 PostCSS + Autoprefixer 按目标浏览器补前缀:
h1,h2,h3,h4,h5,h6 { hyphens: auto;}跑一遍后,hyphens: auto 会被展开成带各厂商前缀的版本(规则在 Autoprefixer 里维护,你不用手抄)。
PostCSS 生态里还有 cssnext(尝鲜未来语法)、CSS Modules(类名作用域)、stylelint(规范与错误检查)等——近一两年才真正「飞入寻常项目」。
代价是:装 PostCSS 往往比装 Sass 更折腾——除了 CLI,还要挑插件、配 browserslist 等。所以很多人把它接进 Grunt / Gulp / webpack,和其它构建任务绑在一起跑。
注意:若你从没搭过现代前端构建,入门可以读我的 Modern JavaScript Explained For Dinosaurs,里面串了 npm、打包等与工具链相关的概念。
「预处理后处理」到底叫什么,业内也吵过(见 CSS-Tricks、这篇辨析)。有人觉得后处理能替代预处理,有人两边一起用。无论如何,想把 CSS 能力榨满,值得学后处理这一套。

使用 CSS 方法实现可维护性
预处理、后处理能减负,但单靠工具仍撑不住大型样式库——于是出现各种 CSS 方法论(约定怎么写、怎么命名)。
核心痛点是 CSS 默认全局:每条规则都可能影响整站。你要么靠命名空间硬扛,要么和 选择器特异性 搏斗。方法论本质是给团队一套可执行的规矩。下面按大致时间扫几派:
OOCSS
OOCSS(2009)强调两件事:结构与皮肤分离(布局类别和配色字体别糊在一起,方便换肤);容器与内容分离(同一「对象」在不同位置样式一致)。原则清楚,细则不算死;后来的 SMACSS 等把它写得更可操作。
SMACSS
SMACSS(2011)把样式分五类:基础、布局、模块、状态、主题,并建议如布局类加 l- / layout-,状态用 is-* 等前缀。比 OOCSS 更细,但归类仍要动脑;BEM 则把命名规则钉得更死,上手路径更短。
BEM
BEM(2010)用 Block__Element—Modifier 把界面拆成块(如 search-form)、块内元素(search-form__button)、状态/变体(search-form__button--disabled)。规则好记,新手也能照猫画虎;代价是类名又长又丑,也和「类名要讲语义」的老教条时常冲突。
Atomic CSS
Atomic CSS(2014,也常叫 utility-first / 函数式 CSS)用大量单用途小类(f6、ph3…)堆在标签上,而不是 search-form__button 这种「语义块名」。第一眼像「反模式」,但讨论也很多:传统分离有时反而让 CSS 绑死 DOM;原子化则是 markup 绑 utility——耦合方向不同,没有绝对高下。
再往后是 CSS-in-JS:承认组件里三者分不开。
JS 中的 CSS
CSS-in-JS(约 2014)把样式写在组件旁边(常和 React 一起讲——React 本身就把「模板」放进 JS)。早期多靠 inline style,后来多在构建期 生成带作用域的 class 再塞进 <style>。
它同样挑战「HTML/CSS/JS 三分离」教条:静态站时代,内容/样式分开很合理;应用时代更自然的切片单位往往是组件。目标就是硬边界:这一块里的样式别漏到隔壁。React 带火组件化后,Angular、Ember、Vue 等也都往这走。CSS-in-JS 仍新,最佳实践还在试。
方法论没有唯一正解——库一大就挑顺手的;这些实验长期看都会沉淀成大家的默认工具箱。
结语
以上就是「现代 CSS」的一条常见路径:用 CSS 做排版与基础样式,用 float / flexbox / grid 做布局,用预处理器扩展语法(变量、mixin 等),用后处理器做前缀、未来语法等变换,再用 OOCSS、BEM、原子化、CSS-in-JS 等方法论缓解全局样式带来的维护成本。高级选择器、过渡与动画、形状、var() 等本文没展开——CSS 要讲的远不止这些;谁说它「简单」,多半只摸过冰山一角。
工具与规范仍在快速迭代,容易让人沮丧;但放在 Web 演化的背景里看,你会理解为何需要这些层抽象。好在一直有人在把最佳实践做成可复用的工具与约定。做前端仍然很有意思,希望这篇能当你的路线图。

再次特别感谢 @ryanqnorth 的 恐龙漫画。
本文为学习目的的个人翻译,译文仅供参考。
原文链接:Modern CSS Explained For Dinosaurs。
版权归原作者或原刊登方所有。本文为非官方译本;如有不妥,请联系删除。