深入了解CSS颜色混合函数color-mix

今天来介绍一下 Chrome 111+正式推出的 CSS颜色混合函数:color-mix()open in new window

所谓颜色混合,顾名思义,就是将两种颜色按照一定的比例混合起来,和调色板比较像。到目前为止,可以说是最强大、最实用的 CSS 颜色处理函数了,有了它,再也不需要用LESS、SASS等预处理工具了,还可以实现颜色动态更新,一起了解一下吧

color-mix语法

首先来看语法,比较简单

color-mix(in lch, plum, pink);
color-mix(in lch, plum 40%, pink);
color-mix(in srgb, #34c9eb 20%, white);
color-mix(in hsl longer hue, hsl(120 100% 50%) 20%, white);

通用语法写作

color-mix(method, color1[ p1], color2[ p2])

主要由 3 部分组成,分别是插值颜色法(method,这里只介绍颜色空间)、基础颜色(color1)和混合颜色(color2)

  • 颜色空间是一个很复杂的概念,这里不做介绍,只需要知道用法就行。
    • 矩形颜色空间。srgb、srgb-linear、lab、oklab、xyz、xyz-d50、xyz-d65
    • 极性颜色空间 。hsl、hwb、lch、oklch

以红色和白色混合为例

color-mix(in srgb, red, white);

混合后的颜色介于红色和白色的中间,相当于把之前的颜色减淡了。

再比如,红色和黄色混合,可以得到橙色

color-mix(in srgb, red, yellow);

这是如何混合的呢,很简单,以rgb为例,其实就是将对应的值取平均数

R = (r1 + r2)/ 2
G = (g1 + g2)/ 2
B = (b1 + b2)/ 2

比如红色是rgb(255,0,0),黄色是rgb(255,255,0),计算如下

R = (255 + 255)/ 2 = 255
G = (0 + 255)/ 2 = 127.5
B = (0 + 0)/ 2 = 0

所以混合的颜色就是rgb(255,127.5,0),也就是橙色

color-mix 混合比例

当然,这只是最简单的情况。在上面的语法中,还可以给颜色设置混合比例,也就是前面语法中的p1和p2。

color-mix(method, color1[ p1], color2[ p2])
  • 由于 CSS 强大的“包容”性,对各种情况都做了兼容,主要分为以下几种情况
    • 如果 和p1都p2被省略,则p1 = p2 = 50%
    • 如果p1省略,则p1 = 100% - p2
    • 如果p2省略,则p2 = 100% - p1
    • 如果p1 = p2 = 0%,则该函数无效
    • 如果p1 + p2 ≠ 100%,则p1' = p1 / (p1 + p2)和p2' = p2 / (p1 + p2),其中p1'和p2'是归一化结果

前面 4 种情况都比较好理解,就是互相补全,另外注意,这两个值都位于0%~100%,超出范围者直接不合法

下面来看最后一种情况,也就是两者相加不等于100%的情况。

这里其实也要分两种,首先是p1+p2>100%的情况,比如

color-mix(in srgb, red 100%, white 100%);

这种混合应该怎么计算呢?

首先还是按照上面的计算规则,算出两者的比例关系

p1' = p1 / (p1 + p2) = 100% / (100% + 100%) = 0.5
p2' = p1 / (p1 + p2) = 100% / (100% + 100%) = 0.5

也就是两者是1:1混合的,在超过100%的情况下,仍然按照100%进行分配,也就是等同于

color-mix(in srgb, red 100%, white 100%);
/* 等同于 */
color-mix(in srgb, red 50%, white 50%);

同理,这种情况下也是等同的,因为都是 4:1

color-mix(in srgb, red 100%, white 25%);
/* 等同于 */
color-mix(in srgb, red 80%, white 20%);

所以,在两者相加大于100%的情况下,可以先计算两者的比例,然后再用100%按比例分配即可。

然后是p1+p2<100%的情况。

起初,我以为也是相同的处理方式,其实不然。以下面这个为例

color-mix(in srgb, red 20%, white 20%);
/* 等同于? */
color-mix(in srgb, red 50%, white 50%);

混合比例其实都是1:1,最后这两者结果会相同吗?

可以明显看到,下面的混合结果颜色跟淡一些。为什么呢,其实可以从最终的混合颜色看出原因

两个实色混合出来居然有了透明度!

这个透明度怎么来的呢?其实就是由于两者混合相加不足100%,刚才这个例子,两者相加20%+20%=40%,所以最终的混合出现了0.4的透明度。

我们再换个例子

color-mix(in srgb, red 40%, white 10%);
/*等同于*/
color-mix(in srgb, red 80%, white 20%); /* 50%透明度 */

从控制台可以看到,刚好就是50%的透明度

因此,在两者相加小于100%的情况下,可以先计算两者的比例,然后再按比例分配,最后再叠加上透明度。这样看来,其实大于100%的情况也可以理解为叠加了一个大于1的透明度,只不过最后解析成了1。

color-mix 实际应用

使用color-mix可以很方便的根据主题色生成与之相对应的邻近色,例如

:root{
  --primary-color: #ffeb3b;
  --primary-secondary-1: color-mix(in srgb, var(--primary-color), #fff 20%);
  --primary-secondary-2: color-mix(in srgb, var(--primary-color), #fff 40%);
  --primary-secondary-3: color-mix(in srgb, var(--primary-color), #fff 60%);
  --primary-secondary-4: color-mix(in srgb, var(--primary-color), #fff 80%);
}

这样在实现一些跟随主题色变化的按钮就非常方便了

button{
  color: var(--primary-color);
  background-color: var(--primary-secondary-4);
  outline: 4px solid var(--primary-secondary-4);
}

如果不考虑兼容性,color-mix无疑是最方便的

兼容性和回退措施

现在主流浏览器已经全部兼容了,放心学习吧。

不过实际工作中,还需要做好回退措施,如果浏览器不兼容怎么办?有些同学可能会想到用 CSS 变量来做回退处理,如下

div{
    --bg: color-mix(in srgb, red, blue);
    background: var(--bg, var(--fallback-color));
}

很可惜,这种方式并不可取。原因在于,CSS 变量只有在未定义时,才会走后面的回退值。这种情况下,仍然取前面的值,从而导致颜色失效。

正确的做法是使用CSS supports进行判断

:root{
  --bg: orange
}

@supports (color:color-mix(in srgb,red,blue)) {
  --bg: color-mix(in srgb, red, blue);
}

这样,在不支持的浏览器中也能使用一个还能接受的回退值,只是不能动态去混合了

CSS 颜色混合的N种方式

透明度

这应该是最容易想到的方式。将一个元素透明度降低不就颜色变浅了吗?

button::before{
  content: '';
  background: var(--primary-color);
  opacity: 0.2
}

多重背景

大家可能都知道,CSS3 背景是支持多重背景的,并且层级是越来越低的

因此,我们可以在主题色上覆盖一层半透明的白色,依然可以将原有颜色“减淡”

由于这里是背景图,所以需要用到渐变,而不是颜色。比如希望主题色减淡到自身的20%,可以覆盖80%透明度的白色,实现如下

button{
  background: linear-gradient(rgba(255,255,255,.8),rgba(255,255,255,.8)), /*半透明白色*/
    linear-gradient(var(--primary-color), var(--primary-color));
}

不过这种方式也有一些缺陷,比如仅适合背景层,如果希望box-shadow、outline这些就不行,这些属性没法叠加多层背景。

动画

主要原理是动画播放次数也是支持小数的,比如设置一个从蓝色到白色的动画,播放次数为0.8,那么在播放到80%的地方就停下来了,这样就得到了颜色减淡的效果

button{
  animation: lighterBackgroundColor .001s 0.8 linear forwards;
  /*播放次数为0.8*/
}
@keyframes lighterBackgroundColor {
  from {
    background-color: var(--primary-color)
  }
  to{
    background-color: #fff
  }
}

相比前一种方式,就没有背景的限制了,任意属性都可以,但是每出现一个属性就需要单独一个动画(因为动画变化的就是属性本身),如果要加一个减淡后的outline-color,应该要这么实现

button{
  animation: lighterBackgroundColor .001s 0.8 linear forwards, 
    lighterOutlineColor .001s 0.8 linear forwards; /*outline*/
  /*播放次数为0.8*/
}
@keyframes lighterBackgroundColor {
  from {
    background-color: var(--primary-color)
  }
  to{
    background-color: #fff
  }
}
/*设置一个outline的动画*/
@keyframes lighterOutlineColor {
  from {
    outline-color: var(--primary-color)
  }
  to{
    outline-color: #fff
  }
}

太繁琐了,有没有简单一点的方法呢?

自定义属性动画

上面将属性作为动画有点浪费,因为变化值都是一样的,有没有办法复用呢?

当然可以,将 CSS 变量作为动画对象,比如--lighterColor


button{
  animation: lighterColor .001s 0.8 linear forwards;
  /*播放次数为0.8*/
}
@keyframes lighterColor {
  from {
    --lighterColor: var(--primary-color)
  }
  to{
    --lighterColor: #fff
  }
}

但是,仅仅这样是不够的,动画并不认识这样的变量,根本不会有动画(就像display一样)

为了让自定义变量也支持动画,需要通过@property定义一下

@property - CSS:层叠样式表 | MDN (mozilla.org)open in new window

@property --lighterColor {
  syntax: '<color>';
  inherits: false;
  initial-value: #fff;
}

相比前面的方式,但是适用性更佳,--lighterColor已经是一个独立的变量了,可以用在任意属性上,比如加个outline

button{
  background-color: var(--lighterColor);
  outline: 4px solid var(--lighterColor);
}

可以看到,outline也轻易的实现了颜色减淡

缺点就是,兼容性欠佳,目前firefox还不支持

方式实现成本适应性代码复用性兼容性
透明度⭐️(低)⭐️(差)⭐️(低)⭐️⭐️⭐️(好)
多重背景⭐️⭐️⭐️⭐️⭐️⭐️
动画⭐️⭐️⭐️(高)⭐️⭐️⭐️⭐️⭐️⭐️⭐️
自定义属性动画⭐️⭐️⭐️⭐️⭐️(强)⭐️⭐️⭐️⭐️⭐️
color-mix⭐️⭐️⭐️⭐️⭐️⭐️⭐️(高)⭐️(差)

总结一下

以上就是本文的全部内容了,相信你会对颜色混合有更深刻的理解,下面总结一下

  1. color-mix其实就是将两种颜色按照一定的比例混合起来,和调色板比较像
  2. 语法很简单,只有3个参数,分别是插值颜色法(method)、基础颜色(color1)和混合颜色(color2)
  3. 插值颜色法可以先不用管,一般用来定义颜色空间,有 hsl、srgb
  4. 两种颜色混合可以设置混合比例,由于 CSS 强大的“包容”性,对各种情况都做了兼容
  5. 在混合比例在两者相加大于100%的情况下,需要先计算两者的比例,然后再用100%按比例分配。
  6. 在混合比例在两者相加小于100%的情况下,会出现透明度
  7. 颜色混合最常见的用途是根据主题色生成与之相对应的邻近色
  8. 目前主流浏览器全兼容,在实际开发中,需要使用CSS supports进行回退处理
贡献者: mankueng