Skip to content

现代CSS语法 ​

元信息 ​

  • 目标:把 CSS 当一门"可编程的样式语言"——掌握原生自定义属性(变量)与 calc/clamp/color-mix/light-dark 等函数,实现主题切换与派生配色,无需任何构建工具;并认清预处理器(SCSS)的历史角色
  • 关键概念:自定义属性、var()、calc、clamp、color-mix、主题变量、预处理器、转译
  • 关联阶段:cp1
  • 常见误区:以为 CSS 变量必须靠 SCSS 等构建工具(原生 --x/var() 运行时可用且可继承);以为深浅主题切换要写两套样式或改 HTML(变量覆盖一处、全站跟随);以为颜色只能一个个挑(color-mix 能从品牌色派生全家)

前置 ​

  • CSS基础:规则语法、级联、引入方式

为什么 CSS 需要"可编程" ​

在本周主线三「可编程 CSS」的地图上,本篇是本体:这门样式语言长出了程序设计特性——指令的值不再写死,而是算出来的。

设想全站换品牌色的场景:色值写死在样式表里,出现在链接、按钮、边框、背景……共 47 处。逐处手改既枯燥又容易漏。早期 CSS 缺乏程序设计特性——没有变量、没有函数、没有控制结构,这正是 SCSS 等预处理器诞生的原因(文末有它的完整故事)。

而最近几年的 CSS 标准已经原生补齐了这些能力:变量、数学函数、颜色函数、嵌套,写进样式表就能在当代浏览器中运行,无需任何构建工具。本篇介绍这套可编程特性,它们是现代 CSS 的主轴。

自定义属性:CSS 的变量 ​

自定义属性(custom property)以 -- 开头,用 var() 函数取用:

<!DOCTYPE html>
<html>
<head>
  <title>Parcel Sandbox</title>
  <meta charset="UTF-8" />
  <link rel="stylesheet" href="/styles.css" />
</head>
<body>
  <h1>Hello world</h1>
</body>
</html>

:root 即文档根元素,在这里声明的变量全局可用,相当于整站的设计常量——把主题色、间距、圆角集中在一处,改一处即全站生效。回到开头的场景:47 处色值收成一个 --brand-color,换色就是改一行。

动手:把示例中 --brand-color 的值换掉,Run 之后数一数页面上几处跟着变。

变量是"活的":继承与作用域 ​

自定义属性沿 DOM 树继承——继承正是裁决链的一环(见CSS基础),且可以在任何元素上重新声明——同一个变量在不同子树里可以取不同的值,这就是主题变量的原理。还是那张诗卡:

<!DOCTYPE html>
<html>
<head>
  <title>Parcel Sandbox</title>
  <meta charset="UTF-8" />
  <link rel="stylesheet" href="/styles.css" />
</head>
<body>
  <h1>Hello world</h1>
</body>
</html>

切主题 = 切换一个属性值,所有样式自动跟随。第 3 周示例"一曲新词酒一杯"的主题切换行为,最现代的实现方式正是这一招——也再次印证"样式与结构分离":HTML 结构原样不动。var() 还支持缺省值,变量未定义时不至于失效:

css
.button { color: var(--btn-text, white); }  /* 未定义 --btn-text 时用 white */

组件级主题 ​

把变量声明收窄到某个类上,同一套组件就能在不同容器里呈现不同外观:

css
.card { --card-bg: #f6f8fa; --card-border: #d0d7de; }
.card--warning { --card-bg: #fff8c6; --card-border: #d4a72c; }

这套"变量 + 覆盖"的模式,就是 design token 思想(第 14 周)的最小雏形。

原生函数:会计算的样式 ​

现代 CSS 内置了一批函数,让样式值可以算出来而不是写死:

css
.content { width: calc(100% - 2rem); }            /* 屏宽减去两侧留白 */
.title   { font-size: clamp(16px, 2vw + 8px, 32px); }  /* 不小不小、随视口流动 */
.sidebar { width: min(300px, 100%); }             /* 理想宽与可用宽取小,永不溢出 */

clamp(最小, 理想, 最大) 一个函数就实现了流动排版——过去要靠媒体查询写好几段(见媒体查询)。

颜色的表示与派生 ​

颜色的写法本身也在演化,一条线是从"名字"走向"数值":

css
color: red;                     /* 颜色名:直白但只有 140 多个 */
color: #ff0000;                 /* 16 进制:最常用 */
color: rgb(255, 0, 0);          /* 红绿蓝分量 */
color: oklch(0.63 0.25 29);     /* 感知均匀的新空间:调亮度时色相不跑偏 */

另一条线是从"孤立的色值"走向"派生的色系"。color-mix() 从一个基准色按比例调出深浅透明:

<!DOCTYPE html>
<html>
<head>
  <title>Parcel Sandbox</title>
  <meta charset="UTF-8" />
  <link rel="stylesheet" href="/styles.css" />
</head>
<body>
  <h1>Hello world</h1>
</body>
</html>

配色从"一堆孤立色值"变成"一个品牌色派生全家",可维护性质变。深浅主题还有一个一步到位的函数——light-dark() 配合 color-scheme,一行声明同时给出两个世界的值:

css
:root {
  color-scheme: light dark;
  --text: light-dark(#222, #eaeaea);
}

两个提醒:其一,配色别随机试,应基于配色方案或设计系统(延伸一节列了入口);其二,同一色值在不同显示器上会有色差——颜色最终由物理设备呈现,电商"色差免责"条款的由来,显示器差异是行业现实而不是代码 bug。

SCSS:预处理器路线 ​

CSS 补齐编程特性的路有两条,理解它们的消长很有价值:

  • 升级 CSS 本身:自定义属性、函数、嵌套进标准,浏览器原生运行——这条路已经胜出
  • 预处理器:发明一门类 CSS 的新语言(SCSS/LESS/Stylus),写完由工具转译(transpile)回普通 CSS

SCSS 是预处理器的代表。变量用 $,支持嵌套:

css
// nav.scss —— SCSS 源码
$nav-color: #f90;
nav {
  width: 100px;
  a { color: $nav-color; }   /* 嵌套:相当于 nav a { ... } */
}
css
/* 转译后的 nav.css —— 浏览器实际收到的 */
nav { width: 100px; }
nav a { color: #f90; }

关键差异在与原生能力的对照:

能力原生 CSSSCSS
变量--x + var(x),运行时、可继承$x,编译时、编译后即消失
函数calc/min/max/clamp/color-mix 等自定义 @function
主题切换变量覆盖,零成本需输出多套类
依赖无,浏览器直接运行需构建工具转译

原生变量的"运行时 + 继承"是 SCSS 编译期变量做不到的——比如上面 [data-theme="dark"] 的运行时覆盖,SCSS 变量编译后就不存在了。这是"新项目优先原生"的根本原因。SCSS 仍有价值的地方是它的写法习惯与庞大的存量项目——今天读老项目要认识它,新项目则先问"原生够不够"。顺带一笔:SCSS 的嵌套写法也回归了原生,CSS Nesting 已进标准,nav { a { … } } 直接写在 .css 文件里就能跑——三大招牌(变量、函数、嵌套)至此全部收编。转译的思路你还会再遇到:第 13 周的构建工具链、babel 转译新版本 JavaScript,都是同一件事。

小结 ​

  • 自定义属性 --x + var() 是运行时的真变量:可继承、可覆盖,主题切换一处改全站跟随
  • calc/min/max/clamp 让尺寸可计算,clamp 一个顶过去几段媒体查询
  • 颜色从写死走向派生:color-mix 出全家、light-dark 出双主题、oklch 感知均匀
  • SCSS 是"CSS 缺编程特性"年代的补丁路线,原生补齐后转为读老项目要认识的方言

延伸 ​