CSS mix-blend-mode 父子元素色彩叠加混合会碰撞出什么样的火花

2,015 阅读5分钟

我正在参加「掘金·启航计划」

mix-blend-mode是一个非常有趣的CSS属性,它可以让你的元素与它的父元素进行混合,混合的子元素可以是任何元素,包括文本、图片、SVG等,接下来就和我一起来看看它的效果吧。

先看效果:

简介

什么是混合模式

在开始之前我们先来了解一下什么是混合模式,混合模式(Blend Mode)是图像处理中的一种技术,它可以让你的图像与背景图像进行混合,从而得到一个新的图像。

混合模式常见于图像处理软件中,比如PS(Photoshop)AI(Illustrator)AE(After Effects)等。

常见的混合模式有:

  • 正片叠底
  • 叠加
  • 柔光
  • 颜色加深
  • 颜色减淡
  • 差值
  • 反差
  • 变暗
  • 变亮
  • 滤色

这些东西通常是需要图像处理软件来完成的,但是今天我们就可以在CSS中实现这些效果了。

什么是mix-blend-mode

mix-blend-modeCSS3中新增的一个属性,它可以让你的元素与它的父元素进行混合,从而得到一个新的图像。

mix-blend-mode的值有:

  • normal:默认值,没有混合效果
  • multiply:正片叠底
  • screen:滤色
  • overlay:叠加
  • darken:变暗
  • lighten:变亮
  • color-dodge:颜色减淡
  • color-burn:颜色加深
  • hard-light:强光
  • soft-light:柔光
  • difference:差值
  • exclusion:排除
  • hue:色相
  • saturation:饱和度
  • color:颜色
  • luminosity:亮度

可以看到这些值就是我们常见的混合模式,这就说明我们可以在CSS中实现这些效果了,接下来我们就来看看每种混合模式有什么区别。

混合模式的效果

正片叠底

正片叠底的作用是让两个图层的颜色相乘,从而得到一个新的图像,叠底就是让底层的颜色影响上层的颜色。

正片叠底是一种非常常见的混合模式,它的效果就是将两个图像的像素点的色值相乘,然后再除以255,得到的结果就是新的像素点的色值,这样就可以得到一个新的图像。

例如两个图像的像素点的色值分别为:

  • 图像1:#ff0000
  • 图像2:#00ff00
  • 结果:#000000

这样就可以得到一个新的图像,它的像素点的色值为#000000

在CSS中我们可以通过mix-blend-mode: multiply来实现这种效果。

<div class="blend-mode">
    <p class="multiply">正片叠底</p>
</div>
<style>
    .blend-mode {
        font-size: 24px;
        font-weight: 700;
        color: #ff0000;
        background: #00ff00;
    }

    p.multiply {
        mix-blend-mode: multiply;
    }
</style>

image.png

这里用代码实现一下正片叠底的计算公式,大家可以自行验证是否正确。

function multiply(color1, color2) {
    const r1 = parseInt(color1.slice(1, 3), 16);
    const g1 = parseInt(color1.slice(3, 5), 16);
    const b1 = parseInt(color1.slice(5, 7), 16);

    const r2 = parseInt(color2.slice(1, 3), 16);
    const g2 = parseInt(color2.slice(3, 5), 16);
    const b2 = parseInt(color2.slice(5, 7), 16);

    const r = Math.round((r1 * r2) / 255);
    const g = Math.round((g1 * g2) / 255);
    const b = Math.round((b1 * b2) / 255);

    return `#${r.toString(16)}${g.toString(16)}${b.toString(16)}`;
}

console.log(multiply('#ff0000', '#00ff00')); // #000

滤色

滤色的作用是将两个图层的之间的颜色叠加,使其产生一种比较明亮的效果。

滤色的效果计算相对来说要复杂一些,它的计算公式是:255 - ( (255-基色) * (255-混合色) ) / 255

例如两个图像的像素点的色值分别为:

  • 混合色:#ff0000
  • 基色:#00ff00
  • 结果:#ffff00

这样就可以得到一个新的图像,它的像素点的色值为#ffff00

在CSS中我们可以通过mix-blend-mode: screen来实现这种效果。

/* 省略其他代码,同第一个示例代码相同 */

p.screen {
    mix-blend-mode: screen;
}

image.png

代码实现:

function screen(baseColor, blendColor) {
    const r1 = parseInt(baseColor.slice(1, 3), 16);
    const g1 = parseInt(baseColor.slice(3, 5), 16);
    const b1 = parseInt(baseColor.slice(5, 7), 16);

    const r2 = parseInt(blendColor.slice(1, 3), 16);
    const g2 = parseInt(blendColor.slice(3, 5), 16);
    const b2 = parseInt(blendColor.slice(5, 7), 16);

    const r = Math.round(255 - ((255 - r1) * (255 - r2)) / 255);
    const g = Math.round(255 - ((255 - g1) * (255 - g2)) / 255);
    const b = Math.round(255 - ((255 - b1) * (255 - b2)) / 255);

    return `#${r.toString(16)}${g.toString(16)}${b.toString(16)}`;
}

console.log(screen('#00ff00', '#ff0000')); // #ffff0

在代码中基色指的是父元素的背景色,混合色指的是字体的颜色。

叠加

叠加的效果是将两个图层的颜色叠加,使其产生一种比较明亮的效果,如果底部的颜色比较明亮则减弱顶部的颜色,如果底部的颜色比较暗则增强顶部的颜色。

叠加的计算公式更加复杂了,它分两种情况:

  1. 如果基色小于等于128,则结果为:基色 * 混合色 / 128
  2. 如果基色大于128,则结果为:255 - ( (255-基色) * (255-混合色) ) / 128

例如两个图像的像素点的色值分别为:

  • 混合色:#ff0000
  • 基色:#00ff00
  • 结果:#00ff00

这样就可以得到一个新的图像,它的像素点的色值为#00ff00

在CSS中我们可以通过mix-blend-mode: overlay来实现这种效果。

p.overlay {
    mix-blend-mode: overlay;
}

image.png

代码实现:

function overlay(baseColor, blendColor) {
    const r1 = parseInt(baseColor.slice(1, 3), 16);
    const g1 = parseInt(baseColor.slice(3, 5), 16);
    const b1 = parseInt(baseColor.slice(5, 7), 16);

    const r2 = parseInt(blendColor.slice(1, 3), 16);
    const g2 = parseInt(blendColor.slice(3, 5), 16);
    const b2 = parseInt(blendColor.slice(5, 7), 16);

    const r = r1 <= 128 ? Math.round((r1 * r2) / 128) : Math.round(255 - ((255 - r1) * (255 - r2)) / 128);
    const g = g1 <= 128 ? Math.round((g1 * g2) / 128) : Math.round(255 - ((255 - g1) * (255 - g2)) / 128);
    const b = b1 <= 128 ? Math.round((b1 * b2) / 128) : Math.round(255 - ((255 - b1) * (255 - b2)) / 128);

    return `#${r.toString(16)}${g.toString(16)}${b.toString(16)}`;
}

console.log(overlay('#00ff00', '#ff0000')); // #0ff0

变暗

变暗的效果是将两个图层的颜色进行比较,如果混合色比基色暗,则使用混合色,否则使用基色。

变暗的计算公式是:基色和混合色中较小的那个会被保留,另一个会被抛弃。

例如两个图像的像素点的色值分别为:

  • 混合色:#ff0000
  • 基色:#00ff00
  • 结果:#000000

这样就可以得到一个新的图像,它的像素点的色值为#000000

在CSS中我们可以通过mix-blend-mode: darken来实现这种效果。

p.darken {
    mix-blend-mode: darken;
}

image.png

代码实现:

function darken(baseColor, blendColor) {
    const r1 = parseInt(baseColor.slice(1, 3), 16);
    const g1 = parseInt(baseColor.slice(3, 5), 16);
    const b1 = parseInt(baseColor.slice(5, 7), 16);

    const r2 = parseInt(blendColor.slice(1, 3), 16);
    const g2 = parseInt(blendColor.slice(3, 5), 16);
    const b2 = parseInt(blendColor.slice(5, 7), 16);

    const r = Math.min(r1, r2);
    const g = Math.min(g1, g2);
    const b = Math.min(b1, b2);

    return `#${r.toString(16)}${g.toString(16)}${b.toString(16)}`;
}

console.log(darken('#ff0000', '#00ff00')); // #000

变亮

变亮和变暗正好相反。

变亮的计算公式是:基色和混合色中较大的那个会被保留,另一个会被抛弃。

例如两个图像的像素点的色值分别为:

  • 混合色:#ff0000
  • 基色:#00ff00
  • 结果:#ffff00

这样就可以得到一个新的图像,它的像素点的色值为#ffff00

在CSS中我们可以通过mix-blend-mode: lighten来实现这种效果。

p.lighten {
    mix-blend-mode: lighten;
}

image.png

代码实现:

function lighten(baseColor, blendColor) {
    const r1 = parseInt(baseColor.slice(1, 3), 16);
    const g1 = parseInt(baseColor.slice(3, 5), 16);
    const b1 = parseInt(baseColor.slice(5, 7), 16);

    const r2 = parseInt(blendColor.slice(1, 3), 16);
    const g2 = parseInt(blendColor.slice(3, 5), 16);
    const b2 = parseInt(blendColor.slice(5, 7), 16);

    const r = Math.max(r1, r2);
    const g = Math.max(g1, g2);
    const b = Math.max(b1, b2);

    return `#${r.toString(16)}${g.toString(16)}${b.toString(16)}`;
}

console.log(lighten('#ff0000', '#00ff00')); // #ffff0

颜色减淡

颜色减淡的效果是将混合色的颜色添加到基色上,但是如果混合色的颜色比基色的颜色要深,则会使基色的颜色变浅。

颜色加深的计算公式是:基色 / (1 - 混合色)

例如两个图像的像素点的色值分别为:

  • 混合色:#ff0000
  • 基色:#00ff00
  • 结果:#00ff00

这样就可以得到一个新的图像,它的像素点的色值为#00ff00

在CSS中我们可以通过mix-blend-mode: color-dodge来实现这种效果。

p.color-dodge {
    mix-blend-mode: color-dodge;
}

image.png

代码实现:

function computed(Cb, Cs) {
    let B;
    if (Cb == 0) {
        B = 0;
    } else if (Cs == 1) {
        B = 1;
    } else {
        B = Math.min(1, Cb / (1 - Cs));
    }
    return Math.round(B * 255);
}

function colorDodge(baseColor, blendColor) {
    const r1 = parseInt(baseColor.slice(1, 3), 16);
    const g1 = parseInt(baseColor.slice(3, 5), 16);
    const b1 = parseInt(baseColor.slice(5, 7), 16);

    const r2 = parseInt(blendColor.slice(1, 3), 16);
    const g2 = parseInt(blendColor.slice(3, 5), 16);
    const b2 = parseInt(blendColor.slice(5, 7), 16);

    const r = computed(r1 / 255, r2 / 255);
    const g = computed(g1 / 255, g2 / 255);
    const b = computed(b1 / 255, b2 / 255);

    return `#${r.toString(16)}${g.toString(16)}${b.toString(16)}`;
}

console.log(colorDodge('#00ff00', '#ff0000')); // #0ff0

这次的公式有点奇怪,1 代表 255,0 代表 0,因为我找了很多资料,这个公式是靠谱的,所以就沿用0和1的计算方式。

颜色加深

颜色加深的效果是将混合色的颜色添加到基色上,但是如果混合色的颜色比基色的颜色要浅,则会使基色的颜色变深。

颜色加深的计算公式是:1 - (1 - 基色) / 混合色

例如两个图像的像素点的色值分别为:

  • 混合色:#ff0000
  • 基色:#00ff00
  • 结果:#000000

这样就可以得到一个新的图像,它的像素点的色值为#000000

在CSS中我们可以通过mix-blend-mode: color-burn来实现这种效果。

p.color-burn {
    mix-blend-mode: color-burn;
}

image.png

颜色加深的代码实现:

function computed(Cb, Cs) {
    let B;
    if (Cb == 1) {
        B = 1;
    } else if (Cs == 0) {
        B = 0;
    } else {
        B = Math.max(0, 1 - (1 - Cb) / Cs);
    }
    return Math.round(B * 255);
}

function colorBurn(baseColor, blendColor) {
    const r1 = parseInt(baseColor.slice(1, 3), 16);
    const g1 = parseInt(baseColor.slice(3, 5), 16);
    const b1 = parseInt(baseColor.slice(5, 7), 16);

    const r2 = parseInt(blendColor.slice(1, 3), 16);
    const g2 = parseInt(blendColor.slice(3, 5), 16);
    const b2 = parseInt(blendColor.slice(5, 7), 16);

    const r = computed(r1 / 255, r2 / 255);
    const g = computed(g1 / 255, g2 / 255);
    const b = computed(b1 / 255, b2 / 255);

    return `#${r.toString(16)}${g.toString(16)}${b.toString(16)}`;
}

console.log(colorBurn('#00ff00', '#ff0000')); // #0ff0

强光

强光主要用于调整图像的对比度和亮度,当混合色比较亮时,强光模式会让图像更亮,反之则会让图像变暗。

强光的计算公式是:混合色 < 0.5 ? 2 * 基色 * 混合色 : 1 - 2 * (1 - 基色) * (1 - 混合色)

例如两个图像的像素点的色值分别为:

  • 混合色:#ff0000
  • 基色:#00ff00
  • 结果:#ff0000

这样就可以得到一个新的图像,它的像素点的色值为#ff0000

在CSS中我们可以通过mix-blend-mode: hard-light来实现这种效果。

p.hard-light {
    mix-blend-mode: hard-light;
}

image.png

强光的代码实现:

function computed(Cb, Cs) {
    let B;
    if (Cs < 0.5) {
        B = 2 * Cb * Cs;
    } else {
        B = 1 - 2 * (1 - Cb) * (1 - Cs);
    }
    return Math.round(B * 255);
}

function hardLight(baseColor, blendColor) {
    const r1 = parseInt(baseColor.slice(1, 3), 16);
    const g1 = parseInt(baseColor.slice(3, 5), 16);
    const b1 = parseInt(baseColor.slice(5, 7), 16);

    const r2 = parseInt(blendColor.slice(1, 3), 16);
    const g2 = parseInt(blendColor.slice(3, 5), 16);
    const b2 = parseInt(blendColor.slice(5, 7), 16);

    const r = computed(r1 / 255, r2 / 255);
    const g = computed(g1 / 255, g2 / 255);
    const b = computed(b1 / 255, b2 / 255);

    return `#${r.toString(16)}${g.toString(16)}${b.toString(16)}`;
}

console.log(hardLight('#00ff00', '#ff0000')); // #ff00

柔光

在柔光模式下,如果顶部图层的像素颜色值比底部图层的像素颜色值大,则将其混合到底部图层中;反之,则减少底部图层的亮度。

柔光的计算公式是:混合色 < 0.5 ? 2 * 基色 * 混合色 + 基色 * 基色 * (1 - 2 * 混合色) : sqrt(基色) * (2 * 混合色 - 1) + 2 * 基色 * (1 - 混合色)

例如两个图像的像素点的色值分别为:

  • 混合色:#ff0000
  • 基色:#00ff00
  • 结果:#00ff00

这样就可以得到一个新的图像,它的像素点的色值为#00ff00

image.png

柔光的代码实现:

function computed(Cb, Cs) {
    let B;
    if (Cs < 0.5) {
        B = 2 * Cb * Cs + Cb * Cb * (1 - 2 * Cs);
    } else {
        B = Math.sqrt(Cb) * (2 * Cs - 1) + 2 * Cb * (1 - Cs);
    }
    return Math.round(B * 255);
}

function softLight(baseColor, blendColor) {
    const r1 = parseInt(baseColor.slice(1, 3), 16);
    const g1 = parseInt(baseColor.slice(3, 5), 16);
    const b1 = parseInt(baseColor.slice(5, 7), 16);

    const r2 = parseInt(blendColor.slice(1, 3), 16);
    const g2 = parseInt(blendColor.slice(3, 5), 16);
    const b2 = parseInt(blendColor.slice(5, 7), 16);

    const r = computed(r1 / 255, r2 / 255);
    const g = computed(g1 / 255, g2 / 255);
    const b = computed(b1 / 255, b2 / 255);

    return `#${r.toString(16)}${g.toString(16)}${b.toString(16)}`;
}

console.log(softLight('#00ff00', '#ff0000')); // #0ff0

差值

排除

色相

饱和度

颜色

亮度

写累了,不想写了,有兴趣的可以自己去实现一下,这些都在规范里面有:ltblendmodegt

后面几个感兴趣的可以自己动手来实现一下代码,这里就不写了。

使用

其实上面的原理没多少会喜欢,大家还是喜欢看实际的效果,那么我们就来看看这些混合模式的实际效果。

首先我上面的示例都是作用在文本上的,实际这些混合模式是可以作用在任何元素上的,比如图片,背景等等。

并且这些元素都是会作为叠加层来进行混合的,也就是说,如果你的元素有背景色,那么这个背景色也会参与到混合中,只要对顶层元素设置了混合模式,那么它的背景色也会参与到混合中。

我们来看看字体颜色与背景色的叠加,这个是最简单的,我们只需要设置一个背景色,然后设置一个字体颜色,然后设置一个混合模式就可以了。

<div class="blend-mode">
    <span>A</span>
    <span>B</span>
    <span>C</span>
    <span>D</span>
</div>
<style>
    .blend-mode {
        width: 200px;
        padding: 20px;
        font-weight: 700;
        font-size: 80px;
        letter-spacing: -.3em;
        text-align: center;
        color: #ff0000;
        background: linear-gradient(45deg, #F53F3F, #F77234, #FF7D00, #F7BA1E, #FADC19, #9FDB1D, #00B42A, #14C9C9, #3491FA, #165DFF, #722ED1);
    }
    
    .blend-mode span:nth-child(1) {
        color: #ff0000;
        mix-blend-mode: multiply;
    }
    .blend-mode span:nth-child(2) {
        color: #ffff00;
        mix-blend-mode: difference;
    }
    .blend-mode span:nth-child(3) {
        color: #00ff00;
        mix-blend-mode: color-dodge;
    }
    .blend-mode span:nth-child(4) {
        color: #0000ff;
        mix-blend-mode: screen;
    }
</style>

image.png

可以看到这样的叠加效果是非常有趣的,这里我只是用了四种混合模式,可以根据自己的需要进行搭配和选择。

我上面的效果虽然说还算可以,但是并没有那么有趣,那就随随便便加点动画,好像突然就变的有趣起来了:

.blend-mode span {
    display: inline-block;
}

.blend-mode span:nth-child(1) {
    color: #ff0000;
    mix-blend-mode: multiply;
    animation: shake 1.3s infinite alternate;
    animation-delay: .3s;
}

.blend-mode span:nth-child(2) {
    color: #ffff00;
    mix-blend-mode: difference;
    animation: shake 1s infinite alternate;
    animation-delay: .6s;
}

.blend-mode span:nth-child(3) {
    color: #00ff00;
    mix-blend-mode: color-dodge;
    animation: shake 1.5s infinite alternate;
    animation-delay: .9s;
}

.blend-mode span:nth-child(4) {
    color: #0000ff;
    mix-blend-mode: screen;
    animation: shake 2s infinite alternate;
    animation-delay: 1.2s;
}

@keyframes shake {
    0% {
        transform: translate(0, -10px);
        color: #3491FA;
    }
    20% {
        color: #722ED1;
    }
    40% {
        color: #F5319D;
    }
    60% {
        color: #A0C95E;
    }
    80% {
        color: #EBD89A;
    }
    100% {
        transform: translate(0, 20px);
    }
}

QQ录屏20230223231412.gif

总结

mix-blend-mode是一个非常有趣的属性,也是非常强大的一个属性,它可以让我们的元素有更多的表现力,让我们的元素更加有趣,更加有生命力。

我上面的案例都是使用的文字相关的效果,但是这个属性并不仅仅局限于文字,它可以作用在任何元素上,比如图片,背景等等,只要对顶层元素设置了混合模式,那么它的背景色也会参与到混合中。

这个属性的兼容性还是很好的,除了IE11以下的版本,其他的浏览器都是支持的,所以可以放心的使用。