1. 前言
大多数开发者处理颜色时,就是从设计稿里复制粘贴一个色值,然后就完事了。
但是!CSS 颜色在过去几年里发生了很多变化!
不仅从 Web 十六进制代码演变成了 hsl() 函数,而且就连你熟知的 rgb() 函数也跟以前不同了!
不信我们接着往下看。
2. 现代 CSS 写法
2.1. 你不需要添加 a了
以前,我们用 rgb() 填写普通的 RGB 颜色值,要想改变不透明度,就必须使用 rgba():
css
.red {
color: rgb(255, 0, 0);
}
.red-50 {
color: rgba(255, 0, 0, 0.5);
}
现在,你可以直接添加第 4 个通道了:
css
.red {
color: rgb(255, 0, 0);
}
.red-50 {
color: rgb(255, 0, 0, 0.5);
}
而且,不用担心浏览器兼容问题,只要你不用支持 IE。
2.2. 空格分隔语法
除此之外,逗号现在也被视为老语法了,对于新的颜色函数,我们甚至不能再使用逗号,只能使用新的空格语法。
css
.red {
color: rgb(255 0 0);
}
.blue {
color: hsl(226 100% 50%);
}
不过,使用空格分隔语法时要注意:你不能为 alpha 通道添加第四个值。
换句话说,这样写 color: rgb(255 0 0 0.5) 是不可以的。
如果你要添加第 4 个值,你需要在字母值之前使用一个正斜杠:
css
.red {
color: rgb(255 0 0);
}
/* 50% 透明 */
.red-50 {
color: rgb(255 0 0 / 0.5);
}
.hsl-red-50 {
color: hsl(0 100% 50% / 0.5);
}
为什么要这么做呢?
因为 CSS 新增了很多颜色函数,统一用斜杠可以快速区分"颜色值"和"透明度",看代码时一目了然。
2.3. hsl() 也变了
单位现在可选了:
css
.red {
color: hsl(0deg 100% 50%);
}
.also-red {
color: hsl(0deg 100 50);
}
.another-red {
color: hsl(0 100% 50%);
}
.this-is-red-too {
color: hsl(0 100 50);
}
小提示: 最好还是保留百分号,因为 VS Code 只有带百分号的才会显示颜色预览。
3. 相对颜色
相对颜色是什么?
简单说就是基于现有颜色生成新颜色。
3.1. 基础用法
我们从一个简单的例子开始:
css
.rgb-red {
color: rgb(from #ff0000 r g b);
}
这行代码的意思是:
基于 #ff0000 这个颜色,提取它的红(r)、绿(g)、蓝(b)值,然后用这些值创建一个新颜色。
结果其实就是 rgb(255 0 0)。
看起来很傻对吧?但重点是下面这个:
css
.rgb-red {
color: rgb(from #ff0000 r g b);
}
/* 轻松创建 50% 透明度版本 */
.rgb-red-50 {
color: rgb(from #ff0000 r g b / 0.5);
}
3.2. 最实用的场景:处理 CSS 变量
以前你想让一个 CSS 变量颜色变透明,得这样:
css
:root {
--color-primary: #2563eb;
--color-primary-transparent: rgba(37, 99, 235, 0.75); /* 手动转换,麻烦! */
}
现在呢,你可以直接这样写:
css
:root {
--color-primary: #2563eb;
}
.semi-transparent-primary-background {
/* 直接基于变量创建透明版本 */
background-color: hsl(from var(--color-primary) h s l / 0.75);
}
就像你有一张照片,以前想调整透明度要重新处理一遍,现在滤镜一点就搞定。
3.3. 快速生成配色方案
我们可以快速创建基础颜色的浅色和深色版本:
css
:root {
--base: hsl(217 73% 50%);
--base-light: hsl(from var(--base) h s 75%); /* 调亮 */
--base-dark: hsl(from var(--base) h s 25%); /* 调暗 */
}
比如实现一个 Toast,它可能基于一种基础颜色,然后创建一种较深的颜色用于文本,一种较浅的颜色用于背景,还需要一个不透明度较低的颜色用于阴影。
以前要 4 个值,现在直接 1 个值搞定:
css
.toast {
--toast-color: #222;
/* 深色文字 */
color: hsl(from var(--toast-color) h s 15%);
/* 原色边框 */
border: 2px solid var(--toast-color);
/* 浅色背景 */
background: hsl(from var(--toast-color) h s 90%);
/* 半透明阴影 */
box-shadow: 0 12px 12px -8px hsl(from var(--toast-color) h s l / 0.325);
}
此时换颜色也超简单:
css
[data-toast="info"] {
--toast-color: #0362fc; /* 蓝色 */
}
[data-toast="error"] {
--toast-color: hsl(0 100% 50%); /* 红色 */
}
一个变量搞定所有颜色变体,优雅!十分优雅!

4. 浅暗主题切换
4.1. 以前的痛苦
不知道你是否实现过网站的浅色和深色主题:
css
:root {
/* 默认浅色主题 */
--text-heading: #000;
--text-body: #212121;
--surface: #efefef;
@media (prefers-color-scheme: dark) {
/* 暗色主题 - 第一遍 */
--text-heading: #fff;
--text-body: #efefef;
--surface: #212121;
}
}
.dark-theme {
/* 暗色主题 - 又写一遍! */
--text-heading: #fff;
--text-body: #efefef;
--surface: #212121;
}
同样的颜色写两遍,一个给媒体查询(自动切换),一个给切换按钮。
改一次要改两个地方,烦死了!
4.2. 现在的解决方案:light-dark()
css
:root {
/* 跟随系统偏好 */
color-scheme: light dark;
/* 一次定义,自动切换 */
--text-heading: light-dark(#000, #fff);
--text-body: light-dark(#212121, #efefef);
--surface: light-dark(#efefef, #212121);
}
light-dark(浅色, 暗色) 就这么简单!系统是浅色就用第一个,暗色就用第二个。
4.3. 添加手动切换按钮
如果让用户手动切换主题:
css
:root {
/* 默认跟随系统 */
color-scheme: light dark;
--text-heading: light-dark(#000, #fff);
--text-body: light-dark(#212121, #efefef);
--surface: light-dark(#efefef, #212121);
}
/* 用户选了浅色,锁定 */
html[data-theme="light"] {
color-scheme: light;
}
/* 用户选了暗色,锁定 */
html[data-theme="dark"] {
color-scheme: dark;
}
4.4. 组件精细控制
如果某个区域必须保持固定颜色,比如某个背景图上必须是白字:
css
.hero {
/* 不管全局主题怎么变,这里永远是浅色主题 */
color-scheme: light;
background: url("light-background.webp");
}
5. 渐变优化
5.1. 以前的痛苦
让我们直接看例子,这是一个从蓝到红的渐变:
css
.gradient {
--color-1: hsl(219 76 41); /* 蓝色 */
--color-2: hsl(357 68 53); /* 红色 */
background: linear-gradient(90deg, var(--color-1), var(--color-2));
}
中间会出现灰扑扑的颜色,不好看:

以前你只能手动加个中间色:
css
.better {
--middle: hsl(271 52 41); /* 紫色 */
background: linear-gradient(90deg, var(--color-1), var(--middle), /* 加一个中间色 */ var(--color-2));
}
是不是好看多了:

但是麻烦呀!我甚至可能需要添加两到三个额外的色阶,以确保它与原设计完全相同。
5.2. 现在的解决办法:指定颜色空间
现在你可以轻松解决,只需要指定一个颜色空间:
css
.better {
/* 用 oklch 颜色空间插值,中间色更鲜艳 */
background: linear-gradient(in oklch 90deg, var(--color-1), var(--color-2));
}
就像拍照时选滤镜,不同的颜色空间会产生不同的中间色效果。oklch 在大多数情况下效果最好。
看下效果对比:

唯一的真正问题是,不同的颜色空间可能更适合不同的渐变,所以有时确实需要花点时间摸索。
可选的颜色空间有:
oklch/lchoklab/labhwbxyz- 不指定默认是
srgb
5.3. 实现彩虹渐变
以前做彩虹要指定每一个颜色。
现在只需要:
css
.rainbow {
/* 从红色绕一整圈再回到红色,走长路径 */
background: linear-gradient(in hsl longer hue 90deg, red, red);
}
longer hue 的意思是"走远路",这样就能经过所有颜色了。

6. 超宽色域------当客户就要那个色
有时候客户会拿着他们的 Logo 说:"我就要这个色,一模一样的!"
问题是,普通的 hex、rgb() 和 hsl() 用的是 sRGB 色域,能表示的颜色有限。
这就像以前电视只能显示几百种颜色,然而现在的手机屏幕能显示几百万种。
6.1. 解决方案:display-p3
为了满足客户的需求,你可以使用 color颜色函数,并使用 display-p3 色域。
css
.vibrant-green {
/* 使用 display-p3 色域,颜色更鲜艳 */
color: color(display-p3 0 1 0);
}
如果浏览器不支持,会自动回退到能显示的最接近颜色,不会出错。
你可以在 Chrome 的开发者工具查看这种颜色:

点击色块,它会显示你选择的颜色,但同时也会显示使用 sRGB 色域的显示器的色域限制。
建议: 除非客户真的非常在意那个特定颜色,一般用不着。但知道有这个方案总是好的。
7. 总结
现在你可以更轻松地写代码了:
- 少打字 - 不用区分
rgba和rgb,用空格代替逗号 - 少定义变量 - 用相对颜色基于一个颜色生成多个变体
- 少写重复代码 -
light-dark()一次定义两套主题 - 更好的渐变 - 指定颜色空间让中间色更漂亮
- 更精确的颜色 - 需要时可以用更宽的色域
最重要的是: 这些特性浏览器支持都很好了(除了 IE,但谁还在乎 IE 呢?)
我是冴羽,10 年笔耕不辍,专注前端领域,更新了 10+ 系列、300+ 篇原创技术文章,翻译过 Svelte、Solid.js、TypeScript 文档,著有小册《Next.js 开发指南》、《Svelte 开发指南》、《Astro 实战指南》。
欢迎围观我的"网页版朋友圈",关注我的公众号:冴羽(或搜索 yayujs),每天分享前端知识、AI 干货。