现代 CSS 颜色使用指南

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 / lch
  • oklab / lab
  • hwb
  • xyz
  • 不指定默认是 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. 总结

现在你可以更轻松地写代码了:

  1. 少打字 - 不用区分 rgbargb,用空格代替逗号
  2. 少定义变量 - 用相对颜色基于一个颜色生成多个变体
  3. 少写重复代码 - light-dark() 一次定义两套主题
  4. 更好的渐变 - 指定颜色空间让中间色更漂亮
  5. 更精确的颜色 - 需要时可以用更宽的色域

最重要的是: 这些特性浏览器支持都很好了(除了 IE,但谁还在乎 IE 呢?)

我是冴羽,10 年笔耕不辍,专注前端领域,更新了 10+ 系列、300+ 篇原创技术文章,翻译过 Svelte、Solid.js、TypeScript 文档,著有小册《Next.js 开发指南》、《Svelte 开发指南》、《Astro 实战指南》。

欢迎围观我的"网页版朋友圈",关注我的公众号:冴羽(或搜索 yayujs),每天分享前端知识、AI 干货。

相关推荐
Rrvive2 小时前
Vue3向全局广播数据变化
javascript·vue.js
cj81402 小时前
动态表单与静态表单性能比较
前端
han_2 小时前
为啥 Array.isArray 判断数组最靠谱?
前端·javascript
前端糕手2 小时前
面试高频版
前端·html
沛沛老爹2 小时前
Web转AI决策篇 Agent Skills vs MCP:选型决策矩阵与评估标准
java·前端·人工智能·架构·rag·web转型
字节架构前端2 小时前
媒体采集标准草案 与 Chromium 音频采集实现简介
前端·chrome·音视频开发
奋斗的小青年!!2 小时前
Flutter在OpenHarmony上实现渐变文字动画的深度优化实践
前端·flutter·harmonyos·鸿蒙
梦6503 小时前
Vue3 计算属性 (computed) 与监听属性 (watch)
前端·javascript·vue.js
六月June June3 小时前
leaflet L.popup().setContent中挂载vue组件
前端·javascript·vue.js