CSS Color Level 5 实战:color-mix()、相对颜色与现代色彩空间

这篇文章围绕 CSS Color Level 5 的 color-mix()、相对颜色语法和现代色彩空间展开,解释它们解决的颜色变量、主题切换和广色域显示等真实工程问题,并给出设计系统落地建议与渐进增强方案。

为什么 CSS 颜色需要一次升级

做前端做到一定年头,很多人都有这么一种体会:CSS 里处理颜色的方式,十几年好像没怎么变过。hex、rgb()hsl(),最多配合 CSS 变量和预处理器的颜色函数,就已经是大多数项目的全部手段了。

AI technology illustration

这套老办法不是不能用,而是在一些真实工程场景里越来越别扭。比如设计系统里定义了一套品牌色,hover 状态需要压暗一点,active 状态需要更暗一点。过去常见的做法是在 Sass 里调 darken()mix(),编译时生成几个色值,然后硬编码到样式里。问题在于,这套色值一旦生成,就和运行时的主题系统没什么关系了。如果用户切换暗色模式,或者业务上需要换肤,老办法要么重新编译,要么写一堆重复的变量。

另一个问题来自设备本身。现在越来越多的手机和笔记本屏幕能够显示广色域,设计师在 display-p3 下调整出来的颜色,到了 CSS 里一旦被转成 sRGB,常常会让人觉得“颜色灰了一层”。换句话说,颜色这件事的复杂度已经超过了旧工具能处理的范围。

CSS Color Level 5 正是冲着这些问题来的。它带来的不是一个单独的函数,而是三个互相支撑的能力:color-mix() 把颜色混合从预处理器搬到了浏览器,相对颜色语法让开发者在已有颜色基础上做通道级调整,而 oklch / oklab 这些新的色彩空间则让这些操作在感知上更可靠。

color-mix():把颜色混合交给浏览器

color-mix() 的用法看起来很直接,就是在一个指定的色彩空间里混合两种颜色。最基本的写法是这样:

color-mix(in oklch, var(--brand) 80%, black);
color-mix(in srgb, #ff9900 40%, #00cc99);

第一个例子把品牌色和黑色按 80% 和 20% 混合,得到一个更深的品牌色。第二个例子在 sRGB 空间里混合橙和青。这里的 in 关键字决定的是插值所用的色彩空间,它不只是一个执行细节,而是会直接影响最终看到的颜色。

在 sRGB 里混合红和蓝,得到的是偏灰的紫色;在 oklab 里混合同样的两个颜色,会因为亮度感知更均匀而保留更干净的饱和感。对设计系统来说,这个差别不是玄学,是实实在在的观感差异。

百分比的使用有一点规则。两个颜色各自携带的百分比代表权重,如果两边都省略,默认就是各 50%。如果合计超过 100%,函数会等比缩放到 100%;如果合计不足 100%,则剩余部分会以透明的形式体现在结果里。实际开发中最常用的写法,仍然是让两个比例相加等于 100%。

真正需要留意的是,color-mix() 并不等同于预处理器里的混色。Sass 的 mix() 本质上是在 sRGB 空间的算术平均,而且它是编译期的产物,最终输出的是一串静态色值。而 color-mix() 是运行时的 CSS 函数,它可以接受 CSS 变量,可以参与主题切换,也能和 transparent 这样的颜色做自由混合。这带来的直接变化是:设计系统不需要为每个按钮状态预先编译出一套色阶,而是用一个变量加一次函数调用动态表达出来。

.btn {
  background: var(--brand);
}
.btn:hover {
  background: color-mix(in oklch, var(--brand) 85%, black);
}
.btn[data-variant='secondary'] {
  --btn-base: var(--gray-6);
}

这段代码没有在任何地方写死“暗色 5%”的具体色值,而是让浏览器在渲染时根据当前变量实时计算。如果项目支持多个主题,每个主题只需要调整 --brand,hover 状态也会跟着变。

相对颜色语法:从已有颜色出发做精细调整

color-mix() 适合做混合,但有些需求更像是“把某个颜色的透明度调低”,或者“在保持色相不变的前提下把明度提高一点”。过去我们通常先转换成 HSL 再手工计算,或者依赖 JS 把颜色拆开。CSS Color Level 5 提供了相对颜色语法,直接给出一个颜色源,然后把它的通道映射到新颜色的对应通道上。

/* 保持相同的色相、饱和度和亮度,只改透明度 */
background: oklch(from var(--brand) l c h / 0.5);

/* 把明度降低 0.1 */
background: oklch(from var(--brand) calc(l - 0.1) c h);

/* 改成用户主题色相 */
background: hsl(from var(--brand) var(--theme-hue) s l);

from 后面的颜色被称为“源色”,然后在新颜色函数中可以直接引用 r、g、b、h、s、l、c、alpha 等通道关键字。它们不是字符串,而是可以参与计算的值。这意味着写样式时,可以像在 JS 里操作颜色那样做算术运算。

一个很常见的场景是阴影和描边。比如需要为按钮生成一个带透明度的边框:

border: 1px solid hsl(from var(--brand) h s l / 0.4);

这块能力对主题系统尤其有用。过去如果要支持品牌色可切换,透明度的版本往往需要额外生成变量,比如 --brand-40。现在可以直接用相对颜色语法在用的时候计算,变量数量会少很多。

这里有一个很容易踩的坑:相对颜色语法中的 from var(--color),如果 --color 本身又是这个函数的一部分,很容易形成循环依赖。

:root {
  --brand: oklch(from var(--brand) calc(l + 0.1) c h);
}

这段代码看起来是想让品牌色变亮一点,实际上会让变量陷入循环定义,结果就是整个属性失效。相对颜色更适合“从静态颜色或主题变量派生新颜色”,不要在源色里引用自己。

为什么现代色彩空间如此重要

很多人一开始不理解,既然 color-mix() 也能指定 sRGB,为什么还要折腾 oklch。这里的关键在于颜色的感知均匀性。

sRGB 和 HSL 都是基于旧显示器的色彩模型。sRGB 的 RGB 通道对亮度的感知不是线性的,HSL 的 H 和 L 更是一对很难直觉调整的感受量。比如在 HSL 里把明度从 50% 调到 60%,对黄色和蓝色产生的“变亮”效果完全不同。

oklch 这一类感知均匀的色彩空间,它的目标就是让数学上的等量变化,在视觉上也看起来等量。这样做的收益在设计系统里非常明显:从品牌色到 hover、active、disabled 的一套色阶,可以用统一的 delta 计算出来,而不是靠设计师肉眼微调。

不同色彩空间的适用性并不相同。下面这张表可以作为选择时的参考。

色彩空间 特点 常见用途 注意事项
sRGB 兼容性最好的 RGB 空间 普通 UI、与设计稿精确对齐 色域窄,直接混合容易偏灰
HSL 类似人的直觉,容易上手 手动调整色相、明度 感知不均匀,明暗变化不稳定
LCH 亮度和色度分离 需要高饱和度颜色 色相变化有边界怪异问题
OKLCH 感知均匀,算法更适合现代 UI 混合、色阶、主题派生 部分旧浏览器不支持

对于大部分前端项目,我的建议是:日常业务代码继续用 sRGB 或 hex 没问题,但凡是需要做颜色计算、混合、主题派生的地方,优先用 oklch。它带来的并不只是“更现代”的感觉,而是切切实实地减少了颜色看起来“脏”的概率。

渐进增强:把这些能力用到真实项目里

前面讲了不少特性,但落到实际项目里,还是绕不开浏览器兼容。

目前 color-mix() 和相对颜色语法在主流浏览器中都已经有相当不错的支持。不过开发时仍然建议采用渐进增强策略,把旧方案作为回退,新能力作为增强。

.btn {
  background: #c43d2e;
}
@supports (background: color-mix(in oklch, red, blue)) {
  .btn {
    background: color-mix(in oklch, var(--brand) 85%, black);
  }
}

借助 @supports,不支持新语法的浏览器会直接使用第一个静态色值,支持的在后面覆盖。这样既不会影响页面可用性,也能让现代浏览器获得更好的颜色表现。

在做设计系统时,可以按这样三个阶段去推进:

  • 先把主题核心变量换成 oklch 或能明确定义的颜色,让后续计算有可靠的基础。
  • 在有需要做 hover、active、边框色、文本次级色的地方,引入 color-mix() 替代预编译的色阶。
  • 对于透明度、亮度微调这类需求,优先使用相对颜色语法,减少冗余变量。

实际项目中比较常见的做法是:先用 CSS 变量定义品牌色,然后用相对颜色派生透明度版本,用 color-mix() 来生成 hover 和 active 色。这样整个主题只需要少量核心变量,就能覆盖大多数颜色状态。

另外要提醒一句:不要让 color-mix 出现在每一条样式里。如果某个颜色被大量组件复用,把它定义成一个 CSS 变量,而不是到处重复调用函数。这样可以避免渲染时重复计算,也方便后续替换。

哪些场景值得用,哪些还需要等等

现在的 CSS 颜色体系比几年前复杂了不少,但并不是所有地方都需要立刻迁移。

如果你的项目只是静态页面,色彩也很少变动,那么继续用 hex 或 rgb 完全没问题。新特性不会让你的页面变好,反而可能增加维护成本。

但如果你的项目有这样的特征:有设计系统、有换肤需求、有颜色深色模式适配,或者团队经常为了颜色微调而反复发版,那 CSS Color Level 5 的这些能力就非常值得落地。它们的价值不只是少写几行 Sass,而是把颜色从“编译期产物”变成了“运行时可计算的值”。这中间的区别,直接决定了主题系统能灵活到什么程度。

还有一个提醒是,不要为了用而用。色彩空间的差异、混合空间的差异,都是真实存在的,但它们对最终用户的影响需要结合具体场景来评估。如果你的界面颜色本来就很收敛,升级到 oklch 的感知差别可能并不大;而在大面积使用渐变、强调色的品牌型界面里,感知差别会明显得多。

总体而言,CSS Color Level 5 是近年 CSS 颜色体系里最重要的一次演进。color-mix() 解决了颜色混合的运行时问题,相对颜色语法解决了精细调整的表达问题,oklch 则让前两者在一个更可靠的视觉模型上工作。这三样东西组合在一起,让前端在颜色这件事上终于有了和设计语言匹配的工具。

原创文章,作者:fudengji,如若转载,请注明出处:https://fudengji.cn/article/818/

(0)
上一篇 11小时前
下一篇 3小时前

相关推荐