{T}

CSS混合模式

SVG 以其无损缩放的特性,为 Web 设计带来了前所未有的清晰度和灵活性。而 CSS 混合模式则为我们提供了丰富多彩的视觉效果。当这两者结合起来时,魔法就发生了!我们不再只是简单地给 SVG 上色、加渐变、阴影或滤镜,而是能创造出更多种类、更炫酷的视觉效果。CSS 混合模式让你的 SVG 图形不再是平平无奇的,而是充满层次感和光影效果,仿佛栩栩如生!

这种视觉上的突破不仅能提升用户体验,还能使你的设计更加炫酷、更具创意,让你的 Web 网站或应用在众多竞争中脱颖而出。将深入探讨如何通过 CSS 混合模式,如多重背景、滤镜效果、颜色叠加等,为 SVG 元素赋予丰富的视觉效果。你将学会如何让 SVG 图形在网页上展现出层次丰富、光影交错的效果,从而提升整体设计的视觉冲击力。

我们不会让你一味地听理论。相反,通过具体的案例分析和动手实践,你将亲自掌握如何在实际项目中运用 CSS 混合模式与 SVG 。通过这些实践练习,你将能够解决 Web 开发中的各种难题,优化你的代码,并确保你的 Web 页面在任何浏览器中都能完美呈现。你将不仅掌握技术,更能在实际项目中灵活运用这些技能,让你的设计既美观又实用。

具体来说,你将学习如何使用 CSS 混合模式增强 SVG 图形效果,设计并实现交互式 SVG 效果,以及创建复杂的 SVG 图形动画和过渡效果。无论是数据可视化、界面图标动效,还是游戏元素,CSS 和 SVG 的结合将为你提供无限的创意空间。

此外,课程将聚焦于实践,通过动手操作和案例分析,你将体验到如何在实际项目中运用CSS混合模式与SVG,从而解决设计中的挑战,优化性能,提升网页的响应性,确保兼容性。你将不仅掌握技术,更能在实际项目中灵活运用这些技能,让你的设计既美观又实用。

一同踏上这段充满创意与探索的旅程,探索 SVG 与 CSS 混合模式的无限可能性,为你的 Web 开发项目注入新的活力与灵感!

SVG 和 CSS 混合模式可以用来做些什么

SVG 和 CSS 混合模式结合使用,可以创造出许多令人惊叹的视觉效果,为 Web 开发带来了无数的可能性。这些技术不仅能提升用户体验,还能解决实际开发中的一些常见问题。以下是几个具体的应用案例。

创建动感的 SVG 气泡

在 Web 中,使用 SVG 图形和 CSS 混合模式可以为标题文字创造出独特的背景效果。例如,将标题文字放置在带有不同颜色形状的背景上,可以使页面更加生动有趣:

这种效果可以用于 Web 宣传页、登录页的标题设计,使其更具吸引力。除此之外,也适用于突出显示的内容区域,如促销广告、重要通知等。

在此基础上,我们还可以用于 Web 上的其他 UI 呈现上,比如带有 SVG 气泡的用户头像:

通过将 SVG 气泡 和 CSS 混合模式以及动画结合起来,可以在 Web 上创建炫酷的动态气泡效果。这些气泡可以自由移动、颜色变换,并且通过混合模式产生层次感和光影效果。

创建动态变化的 Icon 图标

Icon 图标在 Web 应用上被广泛应用,已是习已为常的事情。我想,你有很多种不同的方式将 Icon 图标应用到 Web 网站或应用上。但你可能不知道,有一些小技巧,例如通过 SVG 和 CSS 混合可以使你更灵活的操控 Icon 图标,甚至可以创建动态变化的图标:

创建美丽的混合颜色效果

通过结合 SVG 和 CSS 混合模式,可以创建出美丽崦独特的颜色混合效果。这个技巧不仅能提升 Web 页面的视觉吸引力,还能为 Web 设计师提供更多的创作空间。

如上图的数据图表,有多个 SVG 圆形重叠在一起,通过 CSS 混合模式使这些圆形在颜色上产生有趣的混合效果。这使图表中的数据展示更加直观和美观。

动态改变产品图片颜色

在 Web 设计中,动态改变产品图片的颜色是一个常见的需求,尤其是在展示不同颜色的产品时。通过结合 CSS 混合模式和 SVG,可以实现这个功能。

通过动态颜色的切换,用户可以轻松预览产品的不同颜色,提高购物体验。这种效果非常适用于电商网站、产品展示页面,使产品展示更加丰富和多样化。更为关键的是,结合 SVG 和 CSS 混合模式,Web 开发者只需要写几行代码就可以实现复杂的颜色动态切换效果,既方便又实用。

创建粘性光标效果

通过结合 SVG 滤镜和 CSS 混合模式可以创建有趣的鼠标跟随的效果,例如粘性光标效果。这种效果让鼠标在 Web 页面上留上粘稠而有趣的痕迹,给用户带来一种新奇的交互体验。

这种鼠标跟随的动态效果除了能够吸引用户的注意力,使页面更加生动有趣之外,还能够增加页面的交互趣味性,提升用户体验,为用户带来全新的体验。

在上面列举的例子只是 SVG 和 CSS 混合模式一些常见的应用案例,但这并不是它们的全部。通过这些实际案例,可以看到 SVG 和 CSS 混合模式在网页开发中的强大威力。它们不仅可以解决现实开发中的问题,比如动态颜色切换、产品细节展示、增强用户互动体验等,还能为网页设计带来更多的创新和视觉效果。掌握这些技巧将使你的网页视觉更有竞争力和吸引力。

了解 CSS 混合模式的基本原理

稍微了解 CSS 的同学应该知道,在 CSS 中提供了一些特性,不仅能够创建令人印象深刻的视觉效果,还可以进行图像处理,包括 SVG 图形,使图像在 Web 上更具吸引力。这些功能包括:

  • filter 属性:使用 filter 属性可以对图像进行各种处理,如模糊、亮度调整、对比度增强、饱和度控制等。这些简单但强大的功能可用于改善图像的外观
  • filter() 函数: filter() 函数可以将多个滤镜效果组合在一起,以创建更复杂的图像处理效果。它与 filter 属性不同,它只能运用于值为 <image> 类型的属性上,比如 background-image
  • 混合模式(Blend Mode) :CSS 的混合模式包括 mix-blend-modebackground-blend-mode 两个属性,它们可以图像之间以不同的方式进行混合,如正片叠底、叠加、差值等。利用混合模式可以创建引人注目的效果
  • backdrop-filter 属性:它允许我们对元素的背景进行模糊和处理,创造出毛玻璃效果和其他视觉吸引人的效果

我们主要以 CSS 混合模式为主。我们先从 CSS 混合模式的概念开始!

CSS 混合模式是什么?

维基百科是这样描述混合模式的:“混合模式(或称为混合方式)在数字图像编辑和计算机图形中用于确定两个图层如何混合在一起。大多数应用程序中的默认混合模式是通过顶部层上的内容覆盖底部图层以隐藏底部图层”。

混合模式通过在设计工具中使用,比如 Photoshop、Sketch 或 Figma:

混合模式是通过将两个或更多图层的颜色进行组合,创造出一种合成效果的方法。通过改变颜色的混合方式,你可以实现非常有趣的视觉效果。

CSS 混合模式是一项控制 HTML 元素颜色混合的技术。它让你可以定义两个或多个元素之间的颜色交互,从而创造各种视觉效果。其基本原理是定义了如何将一个元素的颜色与另一个元素的颜色进行混合,从而产生新的颜色效果。这些混合模式灵感来源于图形设计工具中的同名功能,让 Web 开发者在设计或开发网页时实现更多创意和复杂效果。

在 CSS 中,你可以使用 mix-blend-modebackground-blend-mode 属性应用设计工具中的大多数混合模式。其中,mix-blend-mode 会将混合效果应用于整个元素,影响其所有部分,包括伪元素和后代元素;而 background-blend-mode 只会应用于元素的背景。

总的来说,CSS 混合模式是一项强大的技术,可用于文本、图像、背景和其他 HTML 元素,使它们在呈现时以不同的方式混合其颜色。它有助于改善 Web 应用或网站的外观和用户体验,同时让开发者能够实现各种创意和艺术效果。

CSS 混合模式的分类

CSS 混合模式可以按照不同的方式进行分类。首先,如果根据 CSS 属性的使用方式进行分类,它主要分为 mix-blend-modebackground-blend-mode

  • mix-blend-mode 适用于 HTML 元素,它将混合效果应用于整个元素,包括其伪元素和后代元素;
  • background-blend-mode 则是将混合效果应用于元素的背景。

除此之外,如果不按照属性来分,它可以分为可分离和不可分离两种类型:

  • 可分离的混合模式将每个颜色分量(如 RGB)单独考虑,其中包括 normalmultiplyscreenoverlaydarkenlightencolor-dodgecolor-burnhard-lightsoft-lightdifferenceexclusion
  • 不可分离的混合模式则将所有颜色分量平等考虑,其中包括 huesaturationcolorluminosity

另外,还可以根据设计软件中的混合模式进行分类:

  • normal 是混合模式的默认值,表示没有任何混合效果;
  • 变暗模式主要包括 darkenmultiplycolor-burn
  • 变亮模式主要包括 lightenscreencolor-dodge
  • 调整对比度模式主要包括 overlaysoft-lighthard-light
  • 反色模式主要包括 differenceexclusion
  • 更改单个 HSL 模式主要包括 huesaturationluminositycolor

每种混合模式都有其特定的用途和效果,开发者可以根据需要选择合适的混合模式来实现不同的视觉效果。

CSS 混合模式的使用与基础原理

不论是使用 mix-blend-mode 对元素进行混合,还是使用 background-blend-mode 对元素背景层进行混合,它们都需要至少两个元素(或层)来进行混合。这是因为混合模式是根据两种输入颜色创建新颜色的方式,因此,层次结构对计算新颜色的方式至关重要。举例来说:

HTML
<div class="card mix-blend-mode">
    <img src="middle.png" />
</div>

<div class="card background-blend-mode">
</div>
CSS
.card {
    width: 300px;
    aspect-ratio: 3 / 4;


    & img {
        display: block;
        width: 100%;
        height: 100%;
        object-fit: cover;
        object-position: center;
    }
}

.mix-blend-mode {
    position: relative;

    &::before,
    &::after {
        content:"";
        position: absolute;
        inset: 0;
    }

    /* 顶层是一个渐变层 */
    &::before {
        background: linear-gradient(225deg in oklab,oklch(70% 0.5 340) 0%,oklch(90% 0.5 200) 91% 91%);
        z-index: 3;
    }

    /* 中间层是一张图片 */
    & img {
        position: relative;
        z-index: 2;
    }

    /* 底层是一个纯色 */
    &::after {
        background: hsl(2400 60% 50%);
        z-index: 1;
    }
}

.background-blend-mode {
    --bg-top:linear-gradient(225deg in oklab,oklch(70% 0.5 340) 0%,oklch(90% 0.5 200) 91% 91%);
    --bg-middle: url("https://s3-us-west-2.amazonaws.com/s.cdpn.io/153385/jpgls-leaf.jpg");
    --bg-bottom: hsl(240 60% 50%);

    background: var(--bg-top) no-repeat left top, var(--bg-middle) no-repeat center;
    background-color: var(--bg-bottom);
    background-size: cover;
}

接下来,我们来看一下在顶层和中间层使用不同的混合模式的效果,从而了解混合模式每个值所起的作用。

正片叠底:multiply

正片叠底混合模式的工作原理就像是将多个透明图层叠放在一起。在这种模式下,白色像素会变为透明,而黑色像素则会保持为黑色。而介于白色和黑色之间的颜色将会相乘其亮度(光)值。这意味着亮色会变得更亮,而暗色会变得更暗,通常会产生较暗的结果。

CSS
.mix-blend-mode {
    &::before, > img {
        mix-blend-mode: multiply;
    }
}

.background-blend-mode {
    background-blend-mode: multiply;
}

Demo 地址:https://codepen.io/airen/full/VwOKvgz

正如你所看到的,正片叠底混合模式(multiply)简单地将每个颜色通道的值相乘。由于这些值在 01 之间进行了归一化,因此其乘积始终比原始值更暗:

在正片叠底混合模式下,计算每个颜色通道(红、绿、蓝)的新值,新值等于相应颜色通道的两个输入值相乘。举例来说,如果一个像素的背景颜色通道为0.5(半亮度),前景颜色通道为0.8(较亮),则计算结果为 0.5 乘以0.8 等于 0.4。这个过程对每个颜色通道分别进行,得到新的 RGB 值。

屏幕:screen

屏幕混合模式(screen)实际上与正片叠混合模式(multiply)相似,但它反转了输入和结果,因此它的效果与正片叠底效果相反,通常会产生更亮的结果。

CSS
.mix-blend-mode {
    &::before, > img {
        mix-blend-mode: screen;
    }
}

.background-blend-mode {
    background-blend-mode: screen;
}

Demo 地址:https://codepen.io/airen/full/wvbzKbQ

使用屏幕混合模式会将亮度值相乘。同样,我们需要将这些值归一化在 01 之间来计算。

在屏幕混合模式下,计算每个颜色通道的新值,新值等于 1 减去相应颜色通道的两个输入值相乘。这相当于将颜色通道的亮度反转,使其变得更亮。举例来说,如果一个像素的背景颜色通道为 0.5,前景颜色通道为 0.8,则计算结果为1 - (0.5 x 0.8) = 0.6。同样,这个过程针对每个颜色通道分别进行,得到新的 RGB 值。

叠加:overlay

叠加混合模式(overlay)将正片叠底(multiply)和屏幕(screen)两种模式结合起来。基础(背景层)的深色变得更深,而基础(背景层)的浅色变得更亮。中间范围的颜色,比如 50% 的灰色,不受影响。

CSS
.mix-blend-mode {
    &::before, > img {
        mix-blend-mode: overlay;
    }
}

.background-blend-mode {
    background-blend-mode: overlay;
}

Demo 地址:https://codepen.io/airen/full/LYoRGVj

叠加混合模式(overlay)共实是很有趣的。如果背景值较亮(> 127.5),则它会以一半的强度应用屏幕混合(screen),使前景变得更亮;如果背景值较暗( 小于或等于 127.5),则它会以一半的强度应用正片叠底混合(multiply),使前景变得更暗。

叠加混合模式(overlay)的计算方法如下:

  • 如果背景颜色通道值大于 0.5(通常用 255 的一半来表示,即 127.5),则将背景颜色通道值减去 0.5,然后将结果与前景颜色通道值相乘,得到新的颜色通道值。
  • 如果背景颜色通道值小于或等于 0.5 (通常用 255 的一半来表示,即 127.5),则将背景颜色通道值加上 0.5,然后将结果与前景颜色通道值相乘,得到新的颜色通道值。

这个计算过程分别针对每个颜色通道(红、绿、蓝)进行,得到新的 RGB 值。这样,overlay 混合模式会根据背景颜色的亮度来选择是应用正片叠底(使颜色变暗)还是应用屏幕(使颜色变亮),从而产生一种同时调整亮度和对比度的效果。

变暗:darken

变暗混合模式(darken)会比较前景层与背景层的暗色亮度,并选择两者中最暗的部分。它通过比较每个颜色通道的 RGB 值而不是亮度(与正片叠和屏幕混合模式不同)来实现这一点。

CSS
.mix-blend-mode {
    &::before, > img {
        mix-blend-mode: darken;
    }
}

.background-blend-mode {
    background-blend-mode: darken;
}

Demo 地址:https://codepen.io/airen/full/RwmGraJ

变暗混合模式(darken)是根据每个颜色通道的 RGB 值进行计算的:

  • 对于每个颜色通道(红、绿、蓝),比前景层和背景层相应颜色通道的 RGB 值
  • 选择两者中较小的 RGB 值作为新的颜色通道值
  • 使用新的 RGB 值为创建新的颜色,这个颜色通常会比原来的颜色更暗

简单地说,darken 会比较每个颜色的 RGB 值并选择最暗的值,从而创建一个新的颜色。

这个计算过程分别针对每个颜色通道进行,从而实现了变暗混合模式(darken)。它会选择每个颜色通道中更暗的像素值,以创建一个新的颜色,使图像中的颜色更加深沉。

变亮:lighten

变亮混合模式(lighten)刚好与变暗混合模式(darken)相反:

CSS
.mix-blend-mode {
    &::before, > img {
        mix-blend-mode: lighten;
    }
}

.background-blend-mode {
    background-blend-mode: lighten;
}

Demo 地址:https://codepen.io/airen/full/dyEpGpd

变亮混合模式(lighten)做的事情与变暗混合模式(darken)完全相同,只是它选择了最亮的值。

这个计算过程分别针对每个颜色通道进行,从而实现了变亮混合模式。它会选择每个颜色通道中更亮的像素值,以创建一个新的颜色,使图像中的颜色更加明亮。

颜色加深:color-burn

颜色加深混合模式(color-burn)与正片叠底混合模式(multiply)非常相似,但它增加了对比度,导致中间色调更饱和,同时减少了亮光部分。

CSS
.mix-blend-mode {
    &::before, > img {
        mix-blend-mode: color-burn;
    }
}

.background-blend-mode {
    background-blend-mode: color-burn;
}

Demo 地址:https://codepen.io/airen/full/dyEpGOd

颜色加深混合模式(color-burn)的工作方式是反转背景,将其除以前景,然后反转结果:

  • 针对每个颜色通道(红、绿、蓝),首先反转背景层的颜色通道值
  • 然后将反转后的背景层颜色通道值除以前景层的颜色通道值
  • 最后,再次反转结果

这个计算过程会导致颜色加深,尤其是在中间色调和阴影部分,从而增加了对比度并提高了颜色的饱和度。高光部分通常会减少。这使得图像中的阴影区域变得更加突出,但可能会损失一些亮度。这就是颜色加深混合模式的效果。

颜色减淡:color-dodge

如果使用颜色减淡混合模式(color-dodge),它会将背景层颜色变亮以反映前景层颜色。

CSS
.mix-blend-mode {
    &::before, > img {
        mix-blend-mode: color-dodge;
    }
}

.background-blend-mode {
    background-blend-mode: color-dodge;
}

Demo 地址:https://codepen.io/airen/full/RwmGrKY

注意,纯黑色颜色不受颜色减淡模式(color-dodge)影响。

CSS 混合模式中的颜色减淡模式(color-dodge)是通过以下计算方式实现的:

  • 对于每个颜色通道(红、绿、蓝),首先反转前景(源)层的颜色通道值,然后将背景层的颜色通道值除以反转后的前景层的颜色通道值
  • 这个计算将导致背景颜色变亮,以反映前景颜色。颜色通道值越接近白色的地方,变化越明显,而对于纯黑色颜色通道值,不会产生变化
  • 使用新的颜色通道值来创建新的颜色,这个颜色通常比原来的颜色更亮

这个计算过程分别针对每个颜色通道进行,从而实现了颜色减淡混合模式,它将背景颜色变亮,以反映前景颜色。

注意,颜色加深混合模式(color-burn)和颜色减淡混合模式(color-dodge)具有明显的阶梯效应,它们可能产生相当强烈的效果。

硬光:hard-light

使用硬光混合模式(hard-light)会产生鲜明的对比效果。这种混合模式要么进行屏幕混合(screen),要么进行正片叠底混合(multiply)。如果像素值比 50% 灰色更轻,则图像会变亮,就像进行了屏幕混合一样。如果像素值比 50% 灰色更暗,则会进行正片叠底混合。

CSS
.mix-blend-mode {
    &::before, > img {
        mix-blend-mode: hard-light;
    }
}

.background-blend-mode {
    background-blend-mode: hard-light;
}

Demo 地址:https://codepen.io/airen/full/gOJwPmZ

在 CSS 混合模式中,硬光混合模式(hard-light)的计算方式如下:

  • 如果前景颜色(前景图像的颜色)的值大于 127.550%灰度),则将前景颜色应用于屏幕混合模式,以一半的强度
  • 如果前景颜色的值小于或等于 127.5,那么将前景颜色应用于正片叠底混合模式,以一半的强度

这个计算方式会根据前景颜色的亮度来决定是应用屏幕混合还是正片叠底混合,从而产生鲜明的对比效果。如果前景颜色较亮,则会增强图像的亮度,而如果前景颜色较暗,则会增强图像的暗部。这使得图像中的细节更加突出,同时保留了一定的对比度。

柔光:soft-light

柔光混合模式(soft-light)是标准混合模式中最复杂的一种。它产生的效果类似于叠加混合模式(overlay),但更加微妙(对比度较低)。

CSS
.mix-blend-mode {
    &::before, > img {
        mix-blend-mode: soft-light;
    }
}

.background-blend-mode {
    background-blend-mode: soft-light;
}

Demo 地址:https://codepen.io/airen/full/qBGabmV

在 CSS 混合模式中,柔光混合模式(soft-light)的计算方式如下:

  • 将前景翻倍,并反转它
  • 将反转后的前景与背景的平方相乘
  • 将上述结果乘以 2,并将其添加到前景和背景的乘积的两倍上

这个计算方式会产生一种比叠加混合模式更为柔和的效果,保留了图像的细节,但降低了对比度。它常用于创建柔和的照明效果或给图像增加一些微妙的变化。

差异:difference

差异混合模式(difference)的工作方式可以类比为照片底片的效果。它会取每个像素的差异值,反转亮色。如果颜色值相同,则变为黑色。它会在输入颜色之间找到一种颜色。

CSS
.mix-blend-mode {
    &::before, > img {
        mix-blend-mode: difference;
    }
}

.background-blend-mode {
    background-blend-mode: difference;
}

Demo 地址:https://codepen.io/airen/full/oNRzbwq

差异混合模式(difference)会将底部颜色从顶部颜色中减去,任何负数都会变为正数。这个过程产生了一种颜色,该颜色位于输入颜色之间,并突出它们之间的差异。具体计算方式如下:

  • 对比两个输入颜色的每个通道(红、绿、蓝)的值
  • 从顶部(前景)颜色的通道中减去底部(背景)颜色的相应通道值
  • 如果结果为负数,将其转换为正数
  • 使用这些修改后的通道值创建新的颜色

这种混合模式会产生一个颜色,该颜色位于输入颜色之间,并且突出它们之间的差异。如果两个输入颜色相同,结果将是黑色。如果它们有差异,结果将显示这些差异的颜色。这种混合模式用于突出图像中不同的部分,通常产生高对比度的效果。

排除:exclusion

排除混合模式(exclusion)和差异混合模式(difference)非常相似,但不同之处在于,它不会对相同的像素返回黑色,而是返回 50% 的灰色,从而产生了更柔和、对比度较低的结果。

CSS
.mix-blend-mode {
    &::before, > img {
        mix-blend-mode: exclusion;
    }
}

.background-blend-mode {
    background-blend-mode: exclusion;
}

Demo 地址:https://codepen.io/airen/full/YzbGwxj

排除混合模式(exclusion)计算如下:

  • 首先,将每个通道(红、绿、蓝)的颜色值标准化为介于 01 之间的小数
  • 对于每个通道,计算底图(背景)颜色值与顶图(前景)颜色值的乘积的两倍
  • 将每个通道的乘积的两倍从每个通道的颜色值总和中减去
  • 最后,将结果的每个通道的值重新标准化到 01 之间,以获得最终的混合颜色

简单地说,从每个通道的总和中减去每个通道的两倍乘积:

这个过程使得相同颜色的像素产生中性的混合颜色,不同颜色的像素则产生柔和的混合效果,相对于 difference 混合模式来说更加柔和。

注意,其中差异混合模式(difference)和排除混合模式(exclusion)也被称为反转混合模式,因为它们创建的颜色几乎是其中一个输入的相反色。这些混合模式通常用于图像中创建反向效果(类似照片底片效果)。

色相:hue

色相混合模式(hue)将获取前景颜色的色调值,并将其应用于背景颜色的饱和度和亮度。

CSS
.mix-blend-mode {
    &::before, > img {
        mix-blend-mode: hue;
    }
}

.background-blend-mode {
    background-blend-mode: hue;
}

Demo 地址:https://codepen.io/airen/full/JjqRGOM

它的计算步骤如下:

  • 从前景颜色中提取色相值(Hue),保留前景颜色的饱和度(Saturation)和亮度(Luminosity)值
  • 从背景颜色中提取饱和度和亮度值,保留背景颜色的色相值
  • 将前景颜色的色调值应用到背景颜色的饱和度和亮度值上,形成新的颜色

这样,色相混合模式(hue)会改变背景颜色的饱和度和亮度,以匹配前景颜色的色调,从而产生一种新的混合颜色。这个模式通常用于调整颜色的整体外观,而不影响亮度和对比度。

饱和度:saturation

饱和度混合模式(saturation)与色相混合模式(hue)类似,但饱和度混合模式(saturation)会采用前景颜色的饱和度值,同时使用背景颜色的色相和亮度值。即,将前景颜色的饱和度应用于背景颜色的色调和亮度。

CSS
.mix-blend-mode {
    &::before, > img {
        mix-blend-mode: saturation;
    }
}

.background-blend-mode {
    background-blend-mode: saturation;
}

Demo 地址:https://codepen.io/airen/full/ZENpQvB

它的计算步骤如下:

  • 从前景颜色中提取饱和度值(Saturation),保留前景颜色的色相(Hue)和亮度(Luminosity)值
  • 从背景颜色中提取色相和亮度值,保留背景颜色的饱和度值
  • 将前景颜色的饱和度值应用到背景颜色的色相和亮度值上,形成新的颜色

这种混合模式的计算方式是将前景颜色的饱和度应用到背景颜色的色相和亮度上。这样可以改变背景颜色的饱和度,使其与前景颜色更加相似。

颜色:color

颜色混合模式(color)将基于前景颜色的色相(Hue)和饱和度(Saturation)以及背景颜色的亮度(Luminosity)创建一个新的颜色。

CSS
.mix-blend-mode {
    &::before, > img {
        mix-blend-mode: color;
    }
}

.background-blend-mode {
    background-blend-mode: color;
}

Demo 地址:https://codepen.io/airen/full/MWdjKQj

它的计算步骤如下:

  • 从前景颜色中提取色相(Hue)和饱和度值(Saturation),保留前景颜色的亮度(Luminosity)值
  • 从背景颜色中提取亮度值,保留背景颜色的色相和饱和度值
  • 将前景颜色的色相和饱和度值应用到背景颜色的亮度值上,形成新的颜色

亮度:luminosity

亮度混合模式(luminosity)与颜色混合模式(color)刚好相反。它将使用前景颜色的亮度值与背景颜色的色相和饱和度值创建新的颜色。

CSS
.mix-blend-mode {
    &::before, > img {
        mix-blend-mode: luminosity;
    }
}

.background-blend-mode {
    background-blend-mode: luminosity;
}

Demo 地址:https://codepen.io/airen/full/rNgMxdm

它的计算步骤如下:

  • 从前景颜色中提取亮度值(Luminosity),保留前景颜色的色相值(Hue)和饱和度值(Saturation)
  • 从背景颜色中提取色相值和饱和度值,保留背景颜色的亮度值
  • 将前景颜色的亮度值应用到背景颜色的色相值和饱和度值上,形成新的颜色

意,色相混合模式(hue)、饱和度混合模式(saturation)、颜色混合模式(color)和亮度混合模式(luminosity)与其他混合模式不同,因为它们操作的是颜色的色相(Hue)、饱和度(Saturation)和亮度(Luminosity),而不是颜色的 RGB 通道值。

特别声明:CSS 混合模式的计算是一个复杂的过程,W3C 规范详细阐述了它们是如何计算的,感兴趣的话,可以分别查看规范中的《Introduction to compositing》和《Advanced compositing features》。

上面向大家所呈现的是 CSS 混合模式最基础的应用。不过,我们需要知道的是,层次结构对混合模式最终的结果是有关键词影响的。就拿下面这个示例而言,图片在渐变层上面或下面,即使是使用相同的混合模式,最终结果也是不一样的:

CSS
.card::before {
    mix-blend-mode: var(--blend-mode);
}

.card img {
    mix-blend-mode: var(--blend-mode);
}

Demo 地址:https://codepen.io/airen/full/vYwXLjQ

除此之外,在使用 CSS 混合模式时,还有一个细节需要注意。在 CSS 中可以使用 mix-blend-modebackground-blend-mode 属性创建混合效果。虽然这两个属性的值是一样的,但它们的使用是有着本质上区别的。mix-blend-mode 应用于元素上,background-blend-mode 用于元素的背景层上。其中 background-blend-mode 很易于理解,也易于掌握,那是因为 background-blend-mode 仅影响元素的背景效果。可 mix-blend-mode 则不同,它会影响元素及其所有后代元素,所以我们在使用 mix-blend-mode 时要尤其注意。例如:

HTML
<div class="container">
    <div class="card">
        <img src="https://picsum.photos/800/600?random=2" alt="">
        <h3>Modern CSS</h3>
        <p>CSS Blend Mode: mix-blend-mode</p>
    </div>
</div>
CSS
.container {
    background-image: linear-gradient(
        to bottom left in oklab,
        oklch(55% 0.45 350) 0%,
        oklch(100% 0.4 95) 100%
    );

    .card {
        mix-blend-mode: var(--blend-mode, normal);
        background-color: #fff;
        border: 1px solid rgb(0 0 0 / 0.125);
        color: #f5f5f5;
        text-shadow: 1px 1px 0 rgb(0 0 0 / 0.25);

        &::before {
            background-color: color-mix(in oklch, #09f, transparent 60%);
        }
    }
}

Demo 地址:https://codepen.io/airen/full/LYoRGrm

为了避免 mix-blend-mode 对其后代元素产生影响,需要对 HTML 结构做出相应的调整。比如下面这个示例:

HTML
<div class="container">
    <div class="backdrop">
        <p class="shapes">🍃</p>
        <p class="shapes">🍂</p>
        <p class="shapes"></p>
    </div>
    <div class="card">
        <img src="https://picsum.photos/800/600?random=2" alt="">
        <h3>Modern CSS</h3>
        <p>CSS Blend Mode: mix-blend-mode</p>
    </div>
</div>

关键 CSS 代码:

CSS
.backdrop {
    filter: brightness(1.7);

    .shapes {
        mix-blend-mode: multiply;
    }
}

Demo 地址:https://codepen.io/airen/full/jOoMWvq

在这一点上,mix-blend-mode 和 CSS 的 filter 属性是相似的。当然,你可以使用 isolation 属性来阻止混合(稍后会介绍)。

另外, background-blend-mode 还可以使用链式的混合模式。这意味着将多个混合模式一起应用于同一个元素。这可以通过在 CSS 中使用逗号分隔多个混合模式来实现。例如:

CSS
.element {
    background-blend-mode: overlay, multiply, screen;
}

在上面的示例中,将三个不同的混合模式(overlaymultiplyscreen)链接在一起,分别应用于元素的背景。这将导致这些混合模式按顺序依次应用于元素的背景,从而创建复杂的视觉效果。

通过链接多个混合模式,你可以创造出更多样化和有趣的背景效果,以满足不同的设计需求。这使得 CSS 混合模式成为创建独特和吸引人的背景视觉效果的有力工具。例如:

CSS
.card {
    background: var(--bg-layer-1), var(--bg-layer-2), var(--bg-layer-3);
    background-blend-mode: var(--blned-layer-1), var(--blend-layer-2), var(--blend-layer-3);
}

Demo 地址:https://codepen.io/airen/full/KKLgVGM

你可以发挥你的才智,给元素的背景添加一些动效,整体的效果又将完全不一样:

Demo 地址:https://codepen.io/airen/full/jOoMWQq

阻止 CSS 混合模式

要阻止混合模式效果,你可以使用 CSS 的 isolation 属性。将 isolation 属性的值设置为 isolate,它将创建一个新的堆叠上下文,从而防止元素与背景或其他元素混合。这会将元素视为一个独立的图层,不受其他元素的混合影响。

CSS
.element {
    isolation: isolate;
}

这样,元素将不再与其周围的元素混合,而成为一个单独的图层。来看一个简单的示例:

HTML
<div class="container">
    <div class="isolation">
        <div class="card"></div>
    </div>
</div>
CSS
.container {
    --blend: screen;
    --isolation: auto;

    background-image: linear-gradient(
        to bottom left in oklab,
        oklch(55% 0.45 350) 0%,
        oklch(100% 0.4 95) 100%
    );

    .card {
        background-color: #fff;
        mix-blend-mode: var(--blend);
    }

    .isolation {
        isolation: var(--isolation);
    }
}

--isolation 属性为 isolate 时,.isolation 将会把 .card.container 进行隔离,运用在 .card 上的 mix-blend-mode 不会与 .container 的背景进行混合:

Demo 地址:https://codepen.io/airen/full/yLWaerM

但请注意,这不适用于 background-blend-mode,因为 background 属性已经被隔离。

使用 CSS 混合模式增强 SVG 图形

SVG 图形的优势不仅在于它是矢量图形,可以随意放大而保持清晰度,而且还可以直接嵌入到 HTML 中。这意味着在网页开发中,SVG 就像普通的 HTML 元素一样被看待,而且可以像其他 HTML 元素一样被 CSS 样式所控制。所以,可以通过在 SVG 元素上使用 CSS 属性,比如 mix-blend-modebackground-blend-mode,来控制 SVG 图形与周围元素的融合效果。

此外,SVG 图形本身也支持多种样式属性,比如填充颜色、描边、透明度等。将这些属性与 CSS 混合模式结合起来,就可以创造出更加丰富多彩的效果。通过在 SVG 元素上同时使用 CSS 混合模式和 SVG 样式属性,可以创造出各种复杂的视觉效果,使 SVG 图形看起来更加生动、引人注目。换句话说,CSS 混合模式和 SVG 的结合为我们提供了丰富的创意空间,让 SVG 图形的样式和效果变得更加灵活和多样化。

接下来,我们通过实际项目中的真实案例来印证这一点。

创建动感的 SVG 气泡

在 Web 开发的过程中,Web 开发者可以使用 SVG 的 <path> 创建出各式各样的气泡形状,而且这些形状的变换不受任何条件的限制。即使你对 SVG 的 <path> 元素的高级命令不太熟悉的前提下,也可以借助诸如 Figma 之类的图形设计软件和在线工具制作出符合自己需求的气泡形状:

URL:https://blob.sifedine.com/

XML
<svg class="overflow-visible" viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
    <defs>
        <linearGradient id="gradient" x1="0%" y1="0%" x2="0%" y2="100%">
            <stop offset="0%" style="stop-color:#1FFBE8"/>
            <stop offset="100%" style="stop-color:#34FF80"/>
        </linearGradient>
    </defs>
    <path d="M158.5,121Q150,142,133,165.5Q116,189,87.5,180Q59,171,39.5,150Q20,129,35,105.5Q50,82,54.5,55.5Q59,29,84,38Q109,47,124,57Q139,67,153,83.5Q167,100,158.5,121Z" fill="url(#gradient)"/>
</svg>

使用不同的形状可以创造出许多不同的视觉效果。比如,可以使用 GSAP库变换每个形状的路径,从而产生动态效果。

XML
<svg id="svg" viewBox="0 0 1000 1000">
    <path id="path1" />
    <path id="path2" />  
    <path id="path3" />  
</svg>

使用 CSS 给 SVG 添加一点样式:

CSS
@layer demo {
    #svg {
        display: block;
        width: 60vh;
        aspect-ratio: 1;

        path{
            &:nth-child(1) {
                fill: #ffcf4e;
            }
            &:nth-child(2) {
                fill: #FF00FF;
            }
            &:nth-child(3) {
                fill: #0ebeff;
            }
        }
    }
}

接着使用 GASP 来动态创建气泡形状:

JavaScript
// 获取SVG元素
const svg = document.getElementById("svg");
const svgPath1 = document.getElementById("path1");
const svgPath2 = document.getElementById("path2");
const svgPath3 = document.getElementById("path3");

// 定义随机函数
const random = (min, max = min) => {
    if (min > max) [min, max] = [max, min]; // 确保min小于等于max
    return min + (max - min) * Math.random(); // 返回[min, max]范围内的随机数
};

// Cardinal样条函数 - 一种带有张力选项的均匀Catmull-Rom样条
const cardinal = (data, closed, tension) => {
    // 如果数据长度小于1,返回空路径
    if (data.length < 1) return "M0 0";
    // 如果张力参数未提供,则将其设为默认值1
    if (tension == null) tension = 1;

    // 计算数据大小
    let size = data.length - (closed ? 0 : 1);

    // 起始路径
    let path = "M" + data[0].x + " " + data[0].y;

    for (let i = 0; i < size; i++) {
        let p0, p1, p2, p3;

        if (closed) {
            // 如果路径是闭合的
            p0 = data[(i - 1 + data.length) % data.length];
            p1 = data[i];
            p2 = data[(i + 1) % data.length];
            p3 = data[(i + 2) % data.length];
        } else {
            // 如果路径是开放的
            p0 = i == 0 ? data[0] : data[i - 1];
            p1 = data[i];
            p2 = data[i + 1];
            p3 = i + 2 < data.length ? data[i + 2] : data[data.length - 1];
        }

        // 计算控制点
        const x1 = p1.x + ((p2.x - p0.x) / 6) * tension;
        const y1 = p1.y + ((p2.y - p0.y) / 6) * tension;

        const x2 = p2.x - ((p3.x - p1.x) / 6) * tension;
        const y2 = p2.y - ((p3.y - p1.y) / 6) * tension;

        // 添加到路径
        path += ` C${x1} ${y1}, ${x2} ${y2}, ${p2.x} ${p2.y}`;
    }

    // 如果路径是闭合的,添加 "Z",否则返回路径
    return closed ? path + "Z" : path;
};

// 创建气泡函数
const createBlob = (options) => {
    const points = []; // 存储气泡的点
    const path = options.element; // SVG 路径元素
    const slice = (Math.PI * 2) / options.numPoints; // 计算每个点的角度
    const startAngle = Math.random() * Math.PI * 2; // 随机起始角度

    // 创建时间线动画,负责更新每个点的位置
    const tl = gsap.timeline({
        onUpdate: update // 在每次更新时调用 update 函数
    });

    // 循环创建点和动画
    for (let i = 0; i < options.numPoints; i++) {
        const angle = startAngle + i * slice; // 计算每个点的角度
        const duration =
            Math.random() * (options.maxDuration - options.minDuration) + options.minDuration; // 随机持续时间

          // 计算每个点的初始位置
          const point = {
              x: options.centerX + Math.cos(angle) * options.minRadius,
              y: options.centerY + Math.sin(angle) * options.minRadius
          };

          // 使用 GSAP 的时间轴动画,控制点在最小和最大半径之间移动
          const tween = gsap.to(point, {
              duration: duration,
              x: options.centerX + Math.cos(angle) * options.maxRadius,
              y: options.centerY + Math.sin(angle) * options.maxRadius,
              repeat: -1, // 无限重复
              yoyo: true, // 来回摆动
              ease: "sine.inOut" // 使用 Sine.easeInOut 缓动函数
          });

          // 将动画添加到时间线 tl 中,并将点存储在 points 数组中。
          tl.add(tween, -Math.random() * duration);
          points.push(point);
    }

    options.tl = tl; // 保存时间线
    options.points = points; // 保存点数组

    // 更新路径函数
    function update() {
        path.setAttribute("d", cardinal(points, true, 1)); // 重新计算路径
    }

    // 随机填充颜色
    path.style.fill = `oklch(${Math.floor(random(0, 100))}% ${Math.floor(random(0, 100) )}% ${Math.floor(random(0, 360))})`;

    return options; // 返回选项对象
};

// 创建 Blobs
const createBlobs = (configs) => {
    // 对每个配置项创建 Blob
    configs.forEach((config) => {
        createBlob(config);
    });
};

// Blob的配置信息
const BlobsConfigs = [
    {
        element: svgPath1, // SVG路径元素
        numPoints: 10, // 气泡的点数
        centerX: 480, // 气泡的中心 X 坐标
        centerY: 480, // 气泡的中心 Y 坐标
        minRadius: 300, // 最小半径
        maxRadius: 350, // 最大半径
        minDuration: 1, // 动画最短持续时间
        maxDuration: 2 // 动画最长持续时间
    },
    {
        element: svgPath2, // SVG路径元素
        numPoints: 10, // 气泡的点数
        centerX: 500, // 气泡的中心 X 坐标
        centerY: 500, // 气泡的中心 Y 坐标
        minRadius: 280, // 最小半径
        maxRadius: 350, // 最大半径
        minDuration: 2, // 动画最短持续时间
        maxDuration: 3 // 动画最长持续时间
    },
    {
        element: svgPath3, // SVG路径元素
        numPoints: 10, // 气泡的点数
        centerX: 520, // 气泡的中心 X 坐标
        centerY: 520, // 气泡的中心 Y 坐标
        minRadius: 300, // 最小半径
        maxRadius: 350, // 最大半径
        minDuration: 1.5, // 动画最短持续时间
        maxDuration: 2.5 // 动画最长持续时间
    }
];

// 创建所有的 Blob
createBlobs(BlobsConfigs);

你将看到三个带有动画效果的气泡形状(即气泡形状在变换):

接下来,使用 CSS 的 mix-blend-mode 给所有 <path> 元素设置相同的混合模式:

CSS
#svg {
    --blend-mode: normal;

    path {
        mix-blend-mode: var(--blend-mode);
    }
}

尝试着调整下面示例中的混合模式的类型,气泡的呈现效果会随 mix-blend-mode 的值变化:

Demo 地址:https://codepen.io/airen/full/VwOKpYr

接下来,将这个气泡当作其他 Web UI 的背景。例如,上面气泡当作用户头像的背景:

HTML
<div class="avatar">
    <img src="http://i.pravatar.cc/500?img=7" alt="">
    <svg id="svg" viewBox="0 0 1000 1000">
        <path id="path1" />
        <path id="path2" />
        <path id="path3" />
    </svg>
</div>

分别给 pathimg 设置一个混合模式:

CSS
.avatar {
    path {
        mix-blend-mode: difference;
    }

    img {
        mix-blend-mode: exclusion;
    }

    &:hover {
        path {
            mix-blend-mode: exclusion;
        }
        img {
            mix-blend-mode: difference;
        }
    }
}

Demo 地址:https://codepen.io/airen/full/zYQKZdZ

注意,你可以不采用随机颜色,调整 mix-blend-mode 属性的值,达到你期望的视觉效果。

Demo 地址:https://codepen.io/airen/full/ZENpevp

我把上面示例中的 img 替换成了一个表单:

Demo 地址:https://codepen.io/airen/full/GRajmoK

我们再来看一个使用 MorphSVG 插件制作的气泡变形效果,这个效果用作 Web 页面横幅的背景,并且将标题和 SVG 的 <path> 设置了不同的混合模式:

HTML
<section>
    <svg width="967" height="495" viewBox="0 0 967 595" aria-hidden="true">
        <defs>
            <linearGradient id="gradient1" x1="0%" y1="0%" x2="0%" y2="100%">
                <stop offset="0" stop-color="#1FFBE8" />
                <stop offset="1" stop-color="#34FF80" />
            </linearGradient>
            <linearGradient id="gradient2" x1="0%" y1="0%" x2="0%" y2="100%">
                <stop offset="0" stop-color="rgb(251 104 31)" />
                <stop offset="1" stop-color="rgb(255 52 255)" />
            </linearGradient>
            <linearGradient id="gradient3" x1="0%" y1="0%" x2="0%" y2="100%">
                <stop offset="0" stop-color="rgb(240 22 10)" />
                <stop offset="1" stop-color="rgb(163 36 19)" />
            </linearGradient>
            <linearGradient id="gradient4" x1="0%" y1="0%" x2="0%" y2="100%">
                <stop offset="0" stop-color="rgb(3 70 143)" />
                <stop offset="1" stop-color="rgb(74 34 209)" />
            </linearGradient>
        </defs>
        <g fill-rule="nonzero" fill="none">
            <path id="shape_1" d="M221.101537 249.120871c26.405346 53.573775 26.105285 107.848516 8.501721 147.402986-17.703584 39.454332-52.710671 64.088255-91.618548 84.416249-39.007897 20.227855-82.016604 36.249919-125.1253314 25.73544-43.1087273-10.51448-86.31747479-47.465364-94.41911496-90.324385-8.10164016-42.85902 19.10386756-91.626177 53.31079266-147.102573C6.0580017 213.87233 47.566405 151.886971 94.675942 147.280628c47.209558-4.606343 100.120269 48.266467 126.425595 101.840243z" fill="url(#gradient1)" />
            <path id="shape_2" d="M203.14275153 141.5672134C248.32686559 68.49632184 314.74427343 27.5036269 376.4142264 18.85911236c61.62415864-8.44606319 118.3787435 15.53236238 172.85884618 47.55564878 54.43430614 32.22173798 106.7925733 72.53417278 126.86565957 133.19046472 20.07308574 60.65629108 7.9831095 141.58013092-38.10154436 184.13373965-46.08465334 42.55360958-126.31663125 46.49270186-220.0732424 46.99439793-93.71081681.3032447-200.97679476-3.1515038-242.54278566-57.17098087-41.64231455-54.14162064-17.53884653-159.04642197 27.7215918-231.99516917z" fill="url(#gradient2)" />
            <path id="shape_3" d="M674.1432899.37496108c44.77109071-.22025805 81.22510008 17.81019281 102.0705012 42.6815596 20.7450406 24.90596631 25.81403283 56.61995357 26.69927113 89.55174707.78488003 32.966393-2.57947148 67.25097633-23.84847972 92.88642258-21.26900824 25.63544626-60.37523398 42.65464775-91.90615413 34.04334947-31.53092058-8.61129738-55.42069925-42.98808124-81.52471861-84.2931556-26.00366065-41.33967474-54.08828315-89.70973276-41.68606472-123.01170824C576.3827812 18.86370914 629.40511737.52772688 674.1432899.37496108z" fill="url(#gradient3)" />
            <path id="shape_4" d="M671.87878892 219.95253056c34.36766966-51.45824202 83.20985764-79.29801887 127.81857265-83.97550003 44.57074238-4.53580365 84.81818917 14.14255906 123.22545101 38.55390194 38.36929172 24.55302471 75.04008598 54.87698953 87.95839346 99.03421643 12.91830748 44.1572269 2.17395717 102.09585427-32.07191423 131.56809906-34.2458709 29.47224566-92.09697414 30.29847156-159.59650028 28.3121708-67.46155503-2.12798087-144.5854171-7.29998846-173.1419754-47.21878033-28.60841431-40.0086091-8.61155186-114.90568134 25.8079728-166.27410787z" fill="url(#gradient4)" />
        </g>
    </svg>
    <h2>CSS Blend Mode & SVG</h2>
</section>
CSS
path {
    mix-blend-mode: difference;
}

h2 {
    mix-blend-mode: overlay;
}
JavaScript
gsap.config({ trialWarn: false });
gsap.registerPlugin(MorphSVGPlugin);

const tl = gsap.timeline({
    repeat: -1, // 设置重复次数为无限
    yoyo: true, // 添加yoyo选项,使动画往返播放
    defaults: { ease: "power1.inOut", duration: 1.5 } // 设置默认动画选项,包括缓动函数和持续时间
});

// 定义形状变换
const shapeMorphs = [
    {
        target: "#shape_1",
        morphs: [
            "M192.008038 291.5c26.405 53.574 55.199 65.469 37.595 105.024-17.704 39.454-52.711 64.088-91.619 84.416-39.008 20.228-82.017 36.25-125.125 25.735-43.109-10.514-86.317-47.465-94.419-90.324-8.102-42.859 19.104-91.626 53.311-147.103C6.058 213.872 19.963 182.203 67.072 177.597c47.21-4.606 98.63 60.33 124.936 113.903z",
            "M190.1 300c30 60 60 70 40 110 -20 40 -60 70 -100 90 -40 20 -90 30 -130 20 -40 -10 -90 -50 -100 -100 -10 -50 20 -100 60 -160 30 -50 50 -80 100 -90 50 -10 110 60 130 120z",
            "M195.3 290.6c28.1 50.2 50.4 65.3 30.8 100.5 -20 40 -60 70 -100 90 -40 20 -90 30 -130 20 -40 -10 -90 -50 -100 -100 -10 -50 20 -100 60 -160 30 -50 50 -80 100 -90 50 -10 110 60 130 120z",
            "M185 290c26.5 53.5 55.2 65.5 37.6 105 -17.7 39.5 -52.7 64.1 -91.6 84.4 -39 20.2 -82 36.2 -125.1 25.7 -43.1 -10.5 -86.3 -47.5 -94.4 -90.3 -8.1 -42.8 19.1 -91.6 53.3 -147.1 34.2 -55.5 48.1 -87.2 95.2 -91.8 47.2 -4.6 98.6 60.3 124.9 113.9z",
            "M180 300c30 60 60 70 40 110 -20 40 -60 70 -100 90 -40 20 -90 30 -130 20 -40 -10 -90 -50 -100 -100 -10 -50 20 -100 60 -160 30 -50 50 -80 100 -90 50 -10 110 60 130 120z"
        ]
    },
    {
        target: "#shape_2",
        morphs: [
            "M203.142 141.567C248.327 68.496 316.486 37.558 378.156 28.913c61.624-8.446 116.637 5.478 171.117 37.501 54.434 32.222 106.793 72.534 126.866 133.190 20.073 60.656 7.983 141.580-38.102 184.134-46.085 42.554-126.317 46.493-220.073 46.994-93.711.303-183.514-20.546-225.080-74.565-41.642-54.142-35.001-141.652 10.259-214.601z",
            "M195 150c40 -20 80 -10 120 20 40 30 90 50 130 80 40 30 50 70 40 110 -10 40 -40 80 -70 110 -30 30 -70 40 -110 40 -40 0 -80 -20 -120 -40 -40 -20 -80 -50 -100 -100 -20 -50 -30 -100 10 -140z",
            "M200 140c50 -10 100 0 140 40 40 40 90 60 130 90 40 30 60 70 50 120 -10 50 -50 100 -80 130 -30 30 -80 40 -120 30 -40 -10 -90 -40 -130 -70 -40 -30 -80 -60 -100 -110 -20 -50 -20 -110 10 -150z",
            "M210 130c50 -10 90 10 140 50 50 40 110 70 150 110 40 40 60 90 50 140 -10 50 -50 90 -90 120 -40 30 -90 40 -140 30 -50 -10 -100 -40 -140 -80 -40 -40 -80 -80 -90 -130 -10 -50 10 -110 40 -150z",
            "M180 160c50 -20 100 0 150 40 50 40 100 60 140 100 40 40 70 90 60 140 -10 50 -50 100 -80 130 -30 30 -80 40 -120 30 -40 -10 -90 -40 -130 -70 -40 -30 -80 -60 -100 -110 -20 -50 -10 -120 10 -160z"
        ]
    },
    {
        target: "#shape_3",
        morphs: [
            "M674.1432899.37496108c44.77109071-.22025805 81.22510008 17.81019281 102.0705012 42.6815596 20.7450406 24.90596631 25.81403283 56.61995357 26.69927113 89.55174707.78488003 32.966393-2.57947148 67.25097633-23.84847972 92.88642258-21.26900824 25.63544626-60.37523398 42.65464775-91.90615413 34.04334947-31.53092058-8.61129738-55.42069925-42.98808124-81.52471861-84.2931556-26.00366065-41.33967474-54.08828315-89.70973276-41.68606472-123.01170824C576.3827812 18.86370914 629.40511737.52772688 674.1432899.37496108z",
            "M674.143 9.375c44.771 -0.220 81.225 17.810 102.071 42.682 20.745 24.906 25.814 56.620 26.699 89.552 0.785 32.966 -2.579 67.251 -23.848 92.886 -21.269 25.635 -60.375 42.655 -91.906 34.043 -31.531 -8.611 -55.421 -42.988 -81.525 -84.293 -26.004 -41.340 -54.088 -89.710 -41.686 -123.012C576.383 18.864 629.405 0.528 674.143 0.375z",
            "M674.143 9.375c44.771 -0.220 81.225 17.810 102.071 42.682 20.745 24.906 25.814 56.620 26.699 89.552 0.785 32.966 -2.579 67.251 -23.848 92.886 -21.269 25.635 -60.375 42.655 -91.906 34.043 -31.531 -8.611 -55.421 -42.988 -81.525 -84.293 -26.004 -41.340 -54.088 -89.710 -41.686 -123.012C576.383 18.864 629.405 0.528 674.143 0.375z",
            "M674.143 9.375c44.771 -0.220 81.225 17.810 102.071 42.682 20.745 24.906 25.814 56.620 26.699 89.552 0.785 32.966 -2.579 67.251 -23.848 92.886 -21.269 25.635 -60.375 42.655 -91.906 34.043 -31.531 -8.611 -55.421 -42.988 -81.525 -84.293 -26.004 -41.340 -54.088 -89.710 -41.686 -123.012C576.383 18.864 629.405 0.528 674.143 0.375z",
            "M674.143 9.375c44.771 -0.220 81.225 17.810 102.071 42.682 20.745 24.906 25.814 56.620 26.699 89.552 0.785 32.966 -2.579 67.251 -23.848 92.886 -21.269 25.635 -60.375 42.655 -91.906 34.043 -31.531 -8.611 -55.421 -42.988 -81.525 -84.293 -26.004 -41.340 -54.088 -89.710 -41.686 -123.012C576.383 18.864 629.405 0.528 674.143 0.375z"
        ]
    },
    {
        target: "#shape_4",
        morphs: [
            "M590.995 205.653C625.362 154.195 755.089 140.655 799.697 135.977c44.571 -4.536 84.818 14.143 123.225 38.554 38.369 24.553 75.040 54.877 87.958 99.034 12.918 44.157 2.174 102.096 -32.072 131.568 -34.246 29.472 -92.097 30.298 -159.597 28.312 -67.462 -2.128 -164.490 39.919 -193.046 0.000 -28.608 -40.009 -69.591 -176.424 -35.172 -227.792z",
            "M600.995 195.653C635.362 144.195 765.089 130.655 809.697 125.977c44.571 -4.536 84.818 14.143 123.225 38.554 38.369 24.553 75.040 54.877 87.958 99.034 12.918 44.157 2.174 102.096 -32.072 131.568 -34.246 29.472 -92.097 30.298 -159.597 28.312 -67.462 -2.128 -164.490 39.919 -193.046 0.000 -28.608 -40.009 -69.591 -176.424 -35.172 -227.792z",
            "M610.995 185.653C645.362 134.195 775.089 120.655 819.697 115.977c44.571 -4.536 84.818 14.143 123.225 38.554 38.369 24.553 75.040 54.877 87.958 99.034 12.918 44.157 2.174 102.096 -32.072 131.568 -34.246 29.472 -92.097 30.298 -159.597 28.312 -67.462 -2.128 -164.490 39.919 -193.046 0.000 -28.608 -40.009 -69.591 -176.424 -35.172 -227.792z",
            "M620.995 175.653C655.362 124.195 785.089 110.655 829.697 105.977c44.571 -4.536 84.818 14.143 123.225 38.554 38.369 24.553 75.040 54.877 87.958 99.034 12.918 44.157 2.174 102.096 -32.072 131.568 -34.246 29.472 -92.097 30.298 -159.597 28.312 -67.462 -2.128 -164.490 39.919 -193.046 0.000 -28.608 -40.009 -69.591 -176.424 -35.172 -227.792z",
            "M630.995 165.653C665.362 114.195 795.089 100.655 839.697 95.977c44.571 -4.536 84.818 14.143 123.225 38.554 38.369 24.553 75.040 54.877 87.958 99.034 12.918 44.157 2.174 102.096 -32.072 131.568 -34.246 29.472 -92.097 30.298 -159.597 28.312 -67.462 -2.128 -164.490 39.919 -193.046 0.000 -28.608 -40.009 -69.591 -176.424 -35.172 -227.792z"
        ]
    }
];

const createShapeMorphAnimations = (timeline, shapeMorphs) => {
    // 遍历形状变换数组,为每个形状添加变换动画
    shapeMorphs.forEach((shape) => {
        const { target, morphs } = shape;
        morphs.forEach((morph, index) => {
            // 使用timeline的to方法添加形状变换动画,设置目标元素、变换路径、持续时间和缓动函数
            timeline.to(
                target,
                { morphSVG: morph, duration: 1.5, ease: "power1.inOut" },
                index * 1.15 // 设置延迟时间,使每个变换动画依次播放
            );
        });
    });
};

// 使用示例
createShapeMorphAnimations(tl, shapeMorphs);

Demo 地址:https://codepen.io/airen/full/PovGmWP

在 Web 设计中,将标题文本放置在带有不同颜色和形状的背景上是一种有趣且引人注目的效果。这种效果可以使页面更加生动和有趣,尤其当形状的颜色各异时。

创建动态变化的 Icon 图标

当前,SVG 图标是 Web 开发中最常见的图标类型之一。在 Web 开发过程中,有多种不同的方式可以调整 SVG 图标的颜色,例如:

  • 直接修改 SVG 图形元素的 fill 属性,例如设置为 fill: red
  • 将 SVG 图形元素的 fill 属性设置为 currentColor,并通过调整元素的 color 属性来实现颜色变化
  • 使用 CSS 的 filter 属性进行调整
  • 等等 ...

这里我将向大家介绍另一种方式,即使用 CSS 混合模式来调整 SVG 图标的颜色。这个思路来源于 @Lea Verou 创建的 Forkgasm 网站。

URL:https://forkgasm.com/

请注意,卡片上徽标的文本效果,看上去有一个镂空的效果。这当中的奥秘就是徽标应用了一个 mix-blend-mode:screen ,在没有应用 CSS 混合模式之前,它是一个白底黑字的效果:

我简单的复制了了这个效果:

HTML
<div class="badges">
    <span>GREEK</span>
    <span>VEGETARIAN</span>
</div>
CSS
.badges span {
    background: #fff;
    color: #000;
    mix-blend-mode: screen;
}

Demo 地址:https://codepen.io/airen/full/YzbGQaV

这不仅限于 mix-blend-mode: screen。它可以与其他类型混合模式创建有趣效果:

Demo 地址:https://codepen.io/airen/full/abrmyNp

这种方式同样可以应用于 SVG 图标。假设我们有三个 SVG 图标:

我们直接将每个图标的 SVG 代码内联到 HTML中,用一个 <span> 将它包裹起来:

HTML
<div class="icons">
    <span>
        <svg class="icon icon--backward" viewBox="0 0 512 512">
            <path d="M459.5 440.6c9.5 7.9 22.8 9.7 34.1 4.4s18.4-16.6 18.4-29V96c0-12.4-7.2-23.7-18.4-29s-24.5-3.6-34.1 4.4L288 214.3V256v41.7L459.5 440.6zM256 352V256 128 96c0-12.4-7.2-23.7-18.4-29s-24.5-3.6-34.1 4.4l-192 160C4.2 237.5 0 246.5 0 256s4.2 18.5 11.5 24.6l192 160c9.5 7.9 22.8 9.7 34.1 4.4s18.4-16.6 18.4-29V352z" />
        </svg>
    </span>
    <span>
        <svg class="icon icon--play" viewBox="0 0 384 512">
            <path d="M73 39c-14.8-9.1-33.4-9.4-48.5-.9S0 62.6 0 80V432c0 17.4 9.4 33.4 24.5 41.9s33.7 8.1 48.5-.9L361 297c14.3-8.7 23-24.2 23-41s-8.7-32.2-23-41L73 39z" />
        </svg>
    </span>
    <span>
        <svg class="icon icon--forward" viewBox="0 0 512 512">
            <path d="M52.5 440.6c-9.5 7.9-22.8 9.7-34.1 4.4S0 428.4 0 416V96C0 83.6 7.2 72.3 18.4 67s24.5-3.6 34.1 4.4L224 214.3V256v41.7L52.5 440.6zM256 352V256 128 96c0-12.4 7.2-23.7 18.4-29s24.5-3.6 34.1 4.4l192 160c7.3 6.1 11.5 15.1 11.5 24.6s-4.2 18.5-11.5 24.6l-192 160c-9.5 7.9-22.8 9.7-34.1 4.4s-18.4-16.6-18.4-29V352z" />
        </svg>
    </span>
</div>

给它们添加一点默认样式:

CSS
span {
    display: grid;
    place-content: center;
    padding: .5rem;
    border-radius: 50%;
    background: #fff;
    width: 64px;
    aspect-ratio: 1;
    transition: all .2s ease-in-out;
    cursor: pointer;
}

.icon {
    display: block;
    width: 90%;
    height: 90%;
    place-self: center;
    fill: #000;
}

正如你所看到的,现在 SVG 图标放置在一个白底的圆圈内(<span> 容器设置为一个白色圆形),而且图标的填充颜色设置为黑色(fill: #000)。事实上,在这里我们期望的是 SVG 图标要看上去没有设置填充色,它需要一个镂空的效果,能直接看到底较的视频效果。

我想你已经猜到了!我们只需要给 spanmix-blend-mode 的值指定位为 screen 即可:

CSS
span {
    mix-blend-mode: screen;
} 

如此一来,SVG 图标看上去就镂空了(没上去没有填充色):

是不是很方便。而且,如果你希望图标在悬停状态下改变颜色,也非常方便。例如:

CSS
span {
    background: #fff;
    mix-blend-mode: screen;

    &:hover {
        mix-blend-mode: normal;
        background: #005fff;

        .icon {
            fill: #fff;
        }
    }
}

.icon {
    fill: #000;
}

Demo 地址:https://codepen.io/airen/full/vYwXJWz

同样的,你可以尝试着调整 mix-blend-mode 的值,说不定会有令你感到惊艳的效果出现:

Demo 地址:https://codepen.io/airen/full/NWVRaWx

这几个示例向大家展示了如何利用混合模式来调整 SVG 图标的颜色、实现元素的交互效果。正如你所看到的,通过不同类型的混合模式与 SVG 图标结合,可以帮助你动态创建图标颜色。

动态改变产品图颜色

在电商网站或应用中,产品图片的颜色展示对用户体验至关重要。传统上,我们需要为每种颜色的产品准备单独的图片文件。然而,当你有许多产品且每个产品有多种颜色时,如何高效处理这一问题呢?即使你能为每种颜色的每个产品提供单独的图片,这也会大幅增加网站的维护成本和加载时间。

实际上,我们有更好的解决方案。通过一些简单的设置,你可以只为每个产品拍摄一张照片,然后动态更改它的颜色。事实上,如果你愿意,你可以在同一张图片上、同一网页上独立更改所有产品的颜色,而无需重新加载页面。

那么,这是一种什么设置呢?难道是什么魔法?

简单来说,我们通过使用 CSS 混合模式和 SVG,就可以实现动态更改产品图片颜色,从而提升用户体验并简化管理,节约成本。

这种技术方案的基本思路是:

  • 创建 SVG 模板:首先,我们需要一个 SVG 文件来作为颜色变化的基础。
  • 应用混合模式:使用 CSS 的 mix-blend-mode 属性将颜色混合模式应用到 SVG 上。
  • 动态更改颜色:通过 JavaScript 或 CSS 变量,可以动态更改 SVG 的颜色属性,实现颜色交换的效果。

接下来,我们通过实际案例来解决这几个步骤。

Demo 地址:https://codepen.io/airen/full/zYQKEWm

上面这个示例是基于下面这张产品图来实现的:

正如你所看到的,模特身上穿的是一件橙色的 T 恤衫。然后通过 CSS 混合模式和 SVG 的结合,你可以将橙色的 T 恤衫颜色替换成你想要的任何颜色。

实现这个效果,首先你需要一个与 T 恤衫一样形状的 SVG 图形。可以使用诸如 Fimga 这样的图形软件来描绘出所面要的 SVG 图形。这个过程对于 Web 设计师来说非常容易,但对于 Web 开发者而言,还是略有点麻烦。在这里,以 Figma 图形设计软件为例,向大家展示如何获取所需要的 SVG 图形。

首先,使用 Figma 软件打开这张产品图,然后使用 “Frame” 工具,定义一个与图片尺寸一样大小的 Frame 框:

接着使用“钢笔”工具,沿着模特身上的橙色 T 恤衫边缘进行描绘:

这个过程对于 Web 开发者来说是一个痛苦的过程,我们需要增加很多个锚点,沿着橙色 T 恤衫进行描绘。因为只有这样才能描绘出与 T 恤衫外型一模一样的 SVG 图形:

上图中蓝色小圆点就是新增的锚点。

如此一来,与 T 恤衫相吻合的 SVG 图形就绘制出来了。紧接着,只需要将 SVG 图形导出即可。在导出的时候,有一个细节需要注意,我们要导出一个与产品图尺寸一样的 SVG ,而不仅仅是导出我们描绘的黑色 T 恤衫外形。这个很简单,在导出 SVG 之前,先将 “Frame” 中的产品图删除,然后将整个 “Frame” 导出:

在开始使用 SVG 之前,请记得使用 SVGOMG 工具对导出的 SVG 进行优化。

现在,你可以将优化之后的 SVG 代码用于 HTML 中:

HTML
<div class="container">
    <div class="card">
        <!-- 产品图 -->
        <img src="https://static.fedev.cn/damo/tshirt.jpg" alt="">

        <!-- SVG 图形 -->
        <svg width="1280" height="1600" viewBox="0 0 1280 1600">
            <path fill-rule="evenodd" clip-rule="evenodd" d="M506.996 474.5C486.757 485.728 474.915 491.597 453.996 501.5C447.887 504.392 442.756 506.518 437.86 508.546C430.404 511.635 423.491 514.499 414.496 519.501C411.649 521.084 408.951 522.257 406.404 523.364C400.435 525.959 395.292 528.195 390.996 534.501C390.402 535.372 389.829 536.192 389.272 536.987C387.338 539.749 385.614 542.212 383.996 545.5C379.205 555.235 376.913 559.828 371.996 569.501C370.593 572.262 369.277 574.818 368.025 577.249C362.477 588.025 358.178 596.377 352.996 609.5C343.894 632.551 335.496 670.501 335.496 670.501C335.496 670.501 331.142 684.277 327.996 702.5C327.292 706.58 326.64 710.871 325.978 715.225C324.248 726.604 322.452 738.412 319.496 748C316.847 756.591 314.865 761.474 312.139 768.19C311.476 769.825 310.768 771.568 309.996 773.5C308.426 777.43 306.979 780.561 305.583 783.582C303.69 787.681 301.889 791.58 299.996 797.001C299.413 798.67 298.776 800.455 298.118 802.294C294.128 813.462 289.423 826.631 291.996 828.001C293.469 828.785 295.996 829.501 295.996 829.501C295.996 829.501 293.456 834.673 293.996 838.001C294.752 842.659 298.447 844.654 302.457 846.82C303.972 847.638 305.533 848.481 306.996 849.501C310.863 852.195 314.023 853.6 317.45 855.124C319.611 856.084 321.879 857.092 324.496 858.501C330.342 861.646 334.607 864.294 338.918 866.97C342.959 869.478 347.039 872.011 352.496 875.001C357.199 877.577 361.056 879.629 364.751 881.595C370.142 884.464 375.185 887.147 381.996 891.001C383.887 892.07 385.532 893.111 387.099 894.101C390.774 896.426 394.02 898.478 398.996 900.001C405.044 901.851 408.762 902.887 414.996 902.001C415.371 901.947 415.731 901.899 416.078 901.852C418.908 901.468 420.922 901.195 423.496 899.501C426.062 897.812 427.202 895.727 428.478 893.393C429.07 892.311 429.69 891.175 430.496 890.001C431.495 888.545 432.646 887.074 433.828 885.563C436.043 882.731 438.366 879.761 439.996 876.501C442.161 874.026 442.896 871.841 443.317 870.592C443.576 869.821 443.715 869.407 443.996 869.501C444.557 869.688 444.402 872.404 444.201 875.925C444.128 877.205 444.049 878.591 443.996 880.001C443.805 885.08 443.996 890.001 443.996 890.001C443.996 890.001 443.612 895.892 444.496 899.501C444.927 901.261 445.324 902.138 445.826 903.25C446.268 904.23 446.793 905.392 447.496 907.501C447.971 908.927 448.696 914.037 449.571 920.2C450.012 923.311 450.492 926.691 450.996 930.001C452.647 940.825 453.996 951.501 453.996 951.501L457.996 971.501C457.996 971.501 461.094 983.619 462.496 991.501L462.553 991.823C465.231 1006.87 466.791 1015.64 465.996 1031C465.818 1034.44 465.413 1037.06 465.005 1039.7C464.597 1042.33 464.186 1044.99 463.996 1048.5C463.347 1060.52 461.496 1079.5 461.496 1079.5C461.496 1079.5 455.826 1109.9 452.996 1129.5C452.79 1130.93 452.59 1132.31 452.397 1133.65C449.939 1150.7 448.478 1160.83 445.496 1179C442.396 1197.9 436.996 1228 436.996 1228C436.996 1228 431.355 1258.65 429.496 1278.5C428.964 1284.19 428.451 1289.21 427.968 1293.96C426.749 1305.93 425.713 1316.1 424.996 1330.5C424.634 1337.78 424.372 1343.59 424.118 1349.21C423.793 1356.42 423.483 1363.3 422.996 1372.5C422.803 1376.15 422.533 1379.15 422.275 1382.01C421.871 1386.5 421.496 1390.65 421.496 1396.5C421.496 1399.06 421.341 1401.48 421.192 1403.78C420.808 1409.74 420.472 1414.95 422.996 1420C425.887 1425.78 427.755 1426.45 430.291 1427.35C430.824 1427.54 431.387 1427.74 431.996 1428C435.496 1429.5 440.152 1430.45 445.496 1431.5C457.017 1433.77 463.261 1434.11 474.996 1434.5C490.982 1435.03 502.542 1434.48 515.783 1433.84C521.951 1433.54 528.483 1433.23 535.996 1433C548.965 1432.6 559.593 1432.75 569.743 1432.9C572.517 1432.94 575.255 1432.97 577.996 1433C594.656 1433.16 611.049 1433.17 632.496 1433C649.276 1432.87 662.919 1432.98 676.249 1433.08C692.701 1433.2 708.679 1433.33 729.496 1433C736.794 1432.89 743.457 1432.8 749.732 1432.72C775.69 1432.37 794.989 1432.12 824.996 1430.5C837.58 1429.82 848.496 1429.9 858.807 1429.98C879.742 1430.13 898.179 1430.27 922.996 1424C925.549 1423.36 927.875 1422.87 930.069 1422.41C936.37 1421.1 941.59 1420.01 947.996 1416C948.527 1415.67 949.046 1415.35 949.556 1415.03C955.131 1411.57 959.467 1408.87 962.996 1403C965.351 1399.08 966.09 1396.48 966.996 1392C968.559 1384.27 966.958 1378.85 964.778 1371.46C964.685 1371.14 964.591 1370.82 964.496 1370.5C962.722 1364.47 961.228 1359.88 958.996 1354C954.006 1340.86 951.996 1320.5 951.996 1320.5C951.996 1320.5 945.377 1274.97 939.996 1246C936.686 1228.17 933.878 1215.41 930.776 1201.3C928.996 1193.21 927.12 1184.68 924.996 1174.5C923.233 1166.05 921.733 1158.18 920.309 1150.71C918.087 1139.05 916.052 1128.38 913.496 1118C911.759 1110.95 910.45 1103.84 909.136 1096.71C908.296 1092.15 907.454 1087.58 906.496 1083C906.23 1081.73 905.961 1080.45 905.692 1079.17C902.661 1064.74 899.496 1049.68 897.496 1031.5C895.675 1014.95 894.568 1003.7 893.496 987.001C893.308 984.065 893.125 981.396 892.954 978.901C892.161 967.319 891.627 959.518 891.996 946.501C892.424 931.41 893.352 922.945 895.496 908.001C897.416 894.619 899.52 886.065 902.996 873.001C906.013 861.663 911.496 845.501 911.496 845.501C911.496 845.501 911.852 851.126 912.996 854.501C914.892 860.091 916.812 863.888 920.496 868.501C921.565 869.839 922.548 871.188 923.505 872.5C927.331 877.749 930.734 882.419 937.496 883.501C953.216 886.016 965.874 877.749 979.505 868.846C987.553 863.59 995.94 858.112 1005.5 854.501C1011.05 852.4 1015.27 850.457 1019.08 848.697C1026.09 845.463 1031.77 842.846 1042 841.001C1044.4 840.567 1046.8 840.531 1049.24 840.495C1053.29 840.436 1057.46 840.374 1062 838.501C1063.08 838.054 1064.15 837.708 1065.19 837.376C1068.42 836.338 1071.24 835.432 1072.5 832.001C1073.6 828.977 1072.29 825.663 1071.01 822.407C1070.42 820.915 1069.84 819.435 1069.5 818.001C1068.87 815.4 1068.52 813.507 1068.21 811.798C1067.78 809.467 1067.41 807.479 1066.5 804.501C1065.21 800.337 1063.9 797.533 1062.41 794.351C1061.66 792.739 1060.86 791.031 1060 789.001C1058.83 786.257 1057.79 783.999 1056.8 781.849C1055.17 778.324 1053.68 775.093 1052 770.501C1050.05 765.178 1049.02 761.049 1047.94 756.727C1047.12 753.461 1046.28 750.085 1045 746.001C1042.08 736.752 1039.4 729.282 1036.68 721.734C1034.21 714.855 1031.71 707.912 1029 699.501C1024.84 686.612 1022.7 678.195 1020.14 668.089C1019.06 663.843 1017.91 659.299 1016.5 654.001C1015.05 648.551 1013.86 643.91 1012.73 639.507C1010.69 631.523 1008.84 624.322 1006 614.501C1004.78 610.308 1003.8 606.544 1002.87 602.998C1000.33 593.3 998.223 585.233 992.996 574.501C991.266 570.948 990.074 568.623 988.912 566.355C987.631 563.855 986.386 561.425 984.496 557.501C984.073 556.622 983.661 555.761 983.257 554.913C980.683 549.525 978.379 544.701 974.996 539.501C968.216 529.078 959.93 525.6 949.739 521.322C946.968 520.159 944.057 518.937 940.996 517.501C936.4 515.343 932.619 513.841 928.689 512.28C925.828 511.144 922.887 509.976 919.496 508.501C914.22 506.205 910.984 504.623 906.912 502.631C905.297 501.842 903.552 500.988 901.496 500.001C892.688 495.769 886.89 492.821 880.006 489.321C876.86 487.721 873.487 486.006 869.496 484.001C853.804 476.114 830.496 462.501 830.496 462.501C830.496 462.501 814.602 453.278 804.996 446.501C798.167 441.683 786.496 433.001 786.496 433.001C786.496 433.001 786.462 439.517 785.496 444.501C784.579 449.235 783.715 453.189 781.996 457.501C779.028 464.946 776.77 468.562 771.996 475.001C770.672 476.786 769.435 478.479 768.252 480.098C760.825 490.264 755.553 497.478 744.496 506.001C735.557 512.891 730.096 516.623 719.496 520.501C710.048 523.957 704.199 524.519 694.258 525.476L693.996 525.501C684.083 526.454 678.401 526.531 668.496 525.501C660.992 524.72 656.776 523.982 649.496 522.001C644.694 520.694 641.732 519.564 637.652 518.009C636.822 517.692 635.944 517.358 634.996 517.001C629.669 514.994 627.459 513.79 622.496 511.001C622.064 510.758 621.653 510.528 621.26 510.307C617.441 508.169 615.291 506.965 611.496 504.001C602.564 497.022 597.566 491.861 590.496 483.001C583.245 473.913 579.22 468.623 574.496 458.001C573.054 454.757 572.712 453.058 572.02 449.617L571.996 449.501C571.264 445.864 571.137 444.11 571.996 440.501C572.359 438.975 572.959 437.337 573.48 435.914C574.032 434.405 574.496 433.138 574.496 432.501C574.496 431.263 565.948 435.942 561.496 439C559.741 440.206 558.171 441.316 556.668 442.38C553.239 444.807 550.153 446.99 545.996 449.501C542.043 451.888 539.593 453.907 537.084 455.975C534.818 457.842 532.505 459.749 528.996 462C523.251 465.686 518.878 468.047 513.679 470.855C511.626 471.964 509.443 473.143 506.996 474.5Z" />
        </svg>
    </div>
</div>

接下来,使用 CSS 给整个 .cardimgsvg 设置样式。在设置样式时,最为关键的是,要将 svgimg 的大小和位置始终保持一致:

CSS
@layer demo {
    .container {
        position: relative;
    }

    .card {
        --fill-color: #09f650;
        display: grid;
        width: 60vh;
        border-radius: 1em;
        overflow: hidden;
        position: absolute;
        top: 50%;
        left: 50%;
        translate: -50% -50%;

        img {
            display: block;
            width: 100%;
            height: 100%;
        }

        svg {
            position: absolute;
            inset: 0;
            width: 100%;
            height: 100%;
            fill: var(--fill-color);
        }
    }
}

正如你看到的,不管你怎么调整卡片的尺寸,SVG 图形始终与模特身上的橙色 T 恤衫吻合在一起。

现在,你所看到的是模特身上的橙色 T 恤衫变成绿色的,这是因为我们在 CSS 中将 svgfill 属性设置为 var(--fill-color) ,其中 --fill-color 的值是一个绿色(#09f650)。

到这一步,可以说万事俱备,只欠东风。只需要在 CSS 中将 svgmix-blend-mode 设置为 multiply 值即可:

CSS
.card svg {
    fill: var(--fill-color);
    mix-blend-mode: multiply;
}

然后,使用几行简单的 JavaScript 代码,根据用户的输入来改变 --fill-color 的值:

JavaScript
const card = document.querySelector('.card');
const color = document.querySelector('#color');

color.addEventListener('change', etv => {
    card.style.setProperty(`--fill-color`, etv.target.value);
})

这是这么魔性,几个简单地步骤,你就可以为产品图动态调整颜色了:

Demo 地址:https://codepen.io/airen/full/zYQKEWm

注意,这里我们演示的是 mix-blend-mode 属性值为 multiply 的效果,但这并不意味着,只能将 mix-blend-mode 属性设置为 multiply ,你也可以设置为其他值,这些值会根据你输入的填充颜色,动态改变产品图颜色。请尝试着调整下面示例中的混合模式,查看产品图颜色的变化:

Demo 地址:https://codepen.io/airen/full/mdYrpbx

使用类似的原理,你还可以实现更复杂的效果,比如 @jasonday 写的这个这个案例

Demo 地址:https://codepen.io/airen/full/PovGEqd

如果你不想将 SVG 内联到 HTML 中,那么可以将 SVG 文件用于 CSS 中。将其与 CSS 的遮罩 mask 、混合模式 mix-blend-mode 一起使用,同样可以实现相似的效果。例如, @Noah Blon 在 Codepen 上写的一个案例,采用的就是这种方式:

Demo 地址:https://codepen.io/noahblon/full/ZbjmbK (来源于 @Noah Blon)

具体代码不在这里展示,要是感兴趣的话,请查看 Demo 源码

非常简单吧!你现在只需要一个 SVG 图形,然后结合 CSS 混合模式,就能动态更改产品图片颜色,不仅能够显著提升用户体验,还能大幅减少图片管理的工作量和成本。该方案带来的好处是显而易见的。

心动不如行动,你的下一个项目就可以使用这种技术方案!

创建粘性光标效果

这是我们这节课最后一个案例,这个案例的效果来源于 @Manoela Ilic她在 Codrops 上提供了八个不同的效果,我复制了当中的一个效果,向大家展示如何结合 SVG 和 CSS 混合模式来创建粘性光标效果:

Demo 地址:https://tympanus.net/Development/GooeyCursor/index8.htmlGitHub 上可获取 Demo 源码

这个效果利用了 SVG 的滤镜和 CSS 混合模式。它的原理很简单:在页面上创建很多小方块,当鼠标悬停时显示它们,并且通过应用 SVG 滤镜制作出一种粘稠的效果,再加上 CSS 混合模式的运用。通过调整滤镜和混合模式的参数,可以得到各种不同的效果变化,提供了很大的定制空间。这个设计非常直观易懂,可以根据需要进行微调,使得页面看起来更具有吸引力和互动性。

实现这个效果有两个关键的地方。首先,使用 SVG 的高斯模糊(<feGaussianBlur>)、颜色矩阵(<feColorMatrix>)和合成(<feComposite>)等滤镜制作了一种粘稠效果:

  • <feGaussianBlur> :对输入图形应用高斯模糊。
  • <feColorMatrix> :操作模糊图形的颜色。
  • <feComposite> :将原始图形与操作后的版本组合。
XML
<svg>
    <defs>
        <filter id="gooey">
            <feGaussianBlur in="SourceGraphic" result="blur" stdDeviation="3.2" />
            <feColorMatrix in="blur" mode="matrix" values="1 0 0 0 0  0 1 0 0 0  0 0 1 0 0  0 0 0 20 -7" result="goo" />
            <feComposite in="SourceGraphic" in2="goo" operator="atop" />
        </filter>
    </defs>
</svg>

SVG 定义的 #gooey 滤镜通过 CSS 应用到 .cursor__inner 元素上:

CSS
.cursor__inner {
    filter: url(#gooey);
}

注意,在这个示例中,我们把 .cursor__inner 元素 <svg> 元素都放置在 .cursor 容器中:

HTML
<div class="cursor">
    <div class="cursor__inner">
        <!-- 这里将通过 JavaScript 动态插入很多个名为 cursor__inner-box 的 div 元素 -->
    </div>
    <!-- 定义粘稠滤镜效果 -->
    <svg>
        <defs>
            <filter id="gooey">
                <feGaussianBlur in="SourceGraphic" result="blur" stdDeviation="3.2" />
                <feColorMatrix in="blur" mode="matrix" values="1 0 0 0 0  0 1 0 0 0  0 0 1 0 0  0 0 0 20 -7" result="goo" />
                <feComposite in="SourceGraphic" in2="goo" operator="atop" />
            </filter>
        </defs>
    </svg>
</div>

接着,使用 JavaScript 根据可用空间(浏览器视窗大小)动态往 .cursor__inner 容器中添加很多个类名为 .cursor__inner-boxdiv 元素。当光标悬停在这些元素上时,#gooey 滤镜会创建一个平滑的,类似水滴的交互效果。这样就实现一种类似粘稠的光标效果:

通过浏览器开发者调试工具查看代码,不难发现,.cursor 铺满了整个浏览器视窗,并且盖在内容(.content)上面,同时使用 CSS 的网格布局技术,使每个小方块(.cursor__inner-box)以相同的大小,均匀在容器中排列:

CSS
.cursor {
    --size: calc(100vw / var(--columns));
    height: 100%;
    position: fixed;
    width: 100%;
    left: 0;
    top: 0;
    pointer-events: none;
    z-index: 99;

}

.cursor__inner {
    display: grid;
    grid-template-columns: repeat(var(--columns), var(--size));
    filter: url(#gooey);
}

.cursor__inner-box {
    width: var(--size);
    height: var(--size);
    background: var(--cursor-bg);
    opacity: 0;
    border-radius: var(--cursor-radius);
}

前面提到过了,跟随鼠标的每个点具有类似水滴效果是通过 SVG 滤镜实现的。如果你足够仔细的话,不难发现,跟随鼠标移动的小方块,它在每个位置的颜色都有所差异。这个效果是使用 CSS 混合模式实现的,即 .cursor 元素的 mix-blend-mode 属性的值指定为 --cursor-blend-mode 。在这个示例中,自定义属性 --cursor-blend-mode 的值为 overlay

CSS
:root {
    --cursor-blend-mode: overlay;
}
.cursor {
    mix-blend-mode: var(--cursor-blend-mode);
}

最后,通过 JavaScript 的点击事件,制作了一个彩蛋效果,即用户点击屏幕时会触发特殊动画。这个动画是围绕点击位置的所有盒子(.cursor__inner-box)有一错位透明度变化的动画效果:

https://codepen.io/airen/full/ZENpvjB

实现整个效果所用的 JavaScript 代码:

JavaScript
// 获取鼠标位置
const getMousePos = (e) => {
    // 返回包含鼠标在视口中的水平和垂直坐标的对象
    return {
        x: e.clientX, // 返回鼠标在视口中的水平坐标
        y: e.clientY // 返回鼠标在视口中的垂直坐标
    };
};

// 获取窗口大小
const getWinSize = () => {
    // 返回包含窗口内部宽度和高度的对象
    return {
        width: window.innerWidth, // 返回浏览器窗口的内部宽度
        height: window.innerHeight // 返回浏览器窗口的内部高度
    };
};

// 初始化鼠标位置为 (0, 0)
let mousepos = { x: 0, y: 0 };

// 更新 'mousepos' 为当前鼠标位置
const updateMousePos = (ev) => {
    // 调用 getMousePos 函数获取当前鼠标位置,并更新 mousepos 对象
    mousepos = getMousePos(ev); // 更新鼠标位置
};

// 初始化窗口大小
let winsize = getWinSize();

// 获取光标的主要 DOM 元素
const cursorEl = document.querySelector(".cursor");

// 创建并设置 .cursor__inner-box 元素的布局
const layout = (DOM, winsize, settings) => {
    // 获取 CSS 变量 --columns 的值,并将其解析为整数
    const columns = parseInt(
        getComputedStyle(DOM.el).getPropertyValue("--columns"),
        10
    );

    // 计算单元格大小、行数和总单元格数
    const cellSize = winsize.width / columns; // 计算单元格大小
    const rows = Math.ceil(winsize.height / cellSize); // 计算行数
    const cellsTotal = rows * columns; // 计算总单元格数

    // 用于存储单元格元素的字符串
    let innerStr = "";

    // 清空 .cursor__inner 元素的内容
    DOM.inner.innerHTML = "";

    // 获取自定义颜色属性,并根据情况拆分为颜色数组
    const customColorsAttr = DOM.el.getAttribute("data-custom-colors");
    let customColorsArr;
    let customColorsTotal = 0;
    if (customColorsAttr) {
        customColorsArr = customColorsAttr.split(","); // 拆分颜色数组
        customColorsTotal = customColorsArr.length; // 获取颜色数组的长度
    }

    // 根据单元格数量和自定义颜色数组创建 .cursor__inner-box 元素
    for (let i = 0; i < cellsTotal; ++i) {
        innerStr +=
          customColorsTotal === 0
            ? '<div class="cursor__inner-box"></div>'
            : `<div style="transform: scale(${gsap.utils.random(
                0.5,
                2
              )}); background:${
                customColorsArr[Math.floor(gsap.utils.random(0, customColorsTotal))]
              }" class="cursor__inner-box"></div>`;
    }

    // 将生成的 .cursor__inner-box 元素字符串设置为 .cursor__inner 元素的 HTML 内容
    DOM.inner.innerHTML = innerStr;

    // 将 .cursor__inner 元素的子元素集合存储到 DOM.cells 属性中
    DOM.cells = DOM.inner.children;

    // 返回计算得到的布局参数对象
    return { columns, cellSize, rows, cellsTotal };
};

// 根据鼠标位置获取对应的单元格元素
const getCellAtCursor = (DOM, columns, cellSize, cellsTotal) => {
    // 计算鼠标位置所在的列索引
    const columnIndex = Math.floor(mousepos.x / cellSize); // 计算鼠标位置所在的列索引
    const rowIndex = Math.floor(mousepos.y / cellSize); // 计算鼠标位置所在的行索引
    const cellIndex = rowIndex * columns + columnIndex; // 计算鼠标位置所在的单元格索引

    // 如果单元格索引超出范围,打印错误信息并返回空
    if (cellIndex >= cellsTotal || cellIndex < 0) {
        console.error("Cell index out of bounds");
        return null;
    }

    // 返回鼠标位置所在的单元格元素
    return DOM.cells[cellIndex];
};

// 初始化和绑定事件
const initEvents = (DOM, settings, layoutParams) => {
    // 在 'resize' 事件触发时,重新计算布局并更新窗口大小
    window.addEventListener("resize", () => layout(DOM, winsize, settings));

    // 用于缓存上一个悬停的单元格元素
    let cachedCell = null;

    // 'mousemove' 或 'pointermove' 事件触发时,处理鼠标移动
    const handleMove = () => {
        // 获取鼠标位置处的单元格元素
        const cell = getCellAtCursor(
            DOM,
            layoutParams.columns,
            layoutParams.cellSize,
            layoutParams.cellsTotal
        );

        // 如果单元格不存在或与上一个悬停的单元格重复,则不执行后续操作
        if (cell === null || cachedCell === cell) return;
        // 缓存当前单元格
        cachedCell = cell;
        // 设置单元格的透明度为1
        gsap.set(cell, { opacity: 1 });
        // 在一定延迟后将单元格的透明度设置为0
        gsap.set(cell, { opacity: 0, delay: settings.ttl });
    };

    // 监听 'mousemove' 和 'pointermove' 事件
    window.addEventListener("mousemove", handleMove);
    window.addEventListener("pointermove", handleMove, { passive: true });
};

// 创建 GooCursor 实例
const createGooCursor = (DOM_el) => {
    // 初始化 DOM 元素对象
    const DOM = {
        el: DOM_el, // 设置主要 DOM 元素
        inner: DOM_el.querySelector(".cursor__inner"), // 获取 .cursor__inner 元素
        cells: null // 初始化单元格集合
    };

    // 初始化设置对象
    const settings = {
        ttl: DOM_el.getAttribute("data-ttl") || 0.2 // 获取或设置单元格显示时长
    };

    // 初始化布局参数对象
    const layoutParams = layout(DOM, winsize, settings);

    // 初始化和绑定事件
    initEvents(DOM, settings, layoutParams);

    // 返回 GooCursor 实例的属性对象
    return { DOM, settings, layoutParams };
};

// 监听 'resize' 事件,当窗口大小改变时更新窗口大小
window.addEventListener("resize", () => {
    // 调用 getWinSize 函数获取当前窗口大小,并更新 winsize 对象
    winsize = getWinSize(); // 更新窗口大小
});

// 监听 'mousemove' 事件并更新鼠标位置
window.addEventListener("mousemove", updateMousePos);
// 监听 'pointermove' 事件并更新鼠标位置,使用 passive:true 提高性能
window.addEventListener("pointermove", updateMousePos, { passive: true });

// 创建 GooCursor 实例
const goo = createGooCursor(cursorEl);

// 监听点击事件,添加彩蛋效果
window.addEventListener("click", () => {
    // 获取鼠标位置处的单元格索引
    const cellIndex = [...goo.DOM.cells].indexOf(
        getCellAtCursor(
            goo.DOM,
            goo.layoutParams.columns,
            goo.layoutParams.cellSize,
            goo.layoutParams.cellsTotal
        )
    );

    // 如果未找到单元格,则打印错误信息并返回
    if (cellIndex === -1) {
        console.error("No cell found at cursor position");
        return;
    }

    // 创建动画时间线,实现彩蛋效果
    gsap
        .timeline()
        .addLabel("start", 0)
        .to(
            [...goo.DOM.cells],
            {
                duration: 1,
                ease: "power4",
                opacity: 1,
                stagger: {
                    from: cellIndex,
                    each: 0.02,
                    grid: [goo.layoutParams.rows, goo.layoutParams.columns]
                }
            },
            "start"
        )
        .to(
            [...goo.DOM.cells],
            {
                duration: 1,
                ease: "power1",
                opacity: 0,
                stagger: {
                    from: cellIndex,
                    each: 0.03,
                    grid: [goo.layoutParams.rows, goo.layoutParams.columns]
                }
            },
            "start+=0.3"
        );
});

注意,示例的动画效果需要依赖 GSAP 库完成。

在上面的示例基础上,我们只需要调整几个关键的自定义属性的值,例如:

  • 网格列数 --columns ,你可以控制 .cursor__inner-box 的布局方式
  • 混合模式 --cursor-blend-mode ,你可以控制混合模式的效果
  • 使用 --cursor-radius 调整 .cursor__inner-box 的圆角半径,可以控制跟随鼠标的图形的外形

Demo 地址:https://codepen.io/airen/full/OJYRwKw

正如你所看到的,你可以通过 SVG 滤镜结合 CSS 混合模式和 JavaScript 创建一个视觉上令人愉悦的交互效果,当鼠标悬停时,小盒子显示出“粘稠”和平滑的效果。点击事件增加了额外的互动元素,使 Web 上的光标体验更加引人入胜。

SVG 混合模式

在 SVG 滤镜中,有一个叫做 <feBlend> 的元素,它可以将 inin2 两个输入图像混合在一起。通过 mode 属性,可以指定不同的混合模式,如 normalmultiplyscreendarkenlighten 等。这些混合模式与 CSS 中的 mix-blend-modebackground-blend-mode 有些相似。

<feBlend> 是 SVG 的一个滤镜功能,它接受两个输入图像(由 inin2 决定)或其他滤镜的输出结果。然后生成一个新的图像作为滤镜效果的一部分,应用于包含该滤镜的 SVG 元素。这个功能主要用于在 SVG 元素之间进行混合,适用于创建复杂的矢量图形效果。

与 CSS 混合模式相比,<feBlend> 更加强大,因为它可以实现更复杂的图形效果。SVG 滤镜基于图形处理,允许进行像素级的操作,比如模糊、色彩变换和复杂的图形合成。因此,<feBlend> 更适合需要精细控制和复杂效果的场景,比如高级图形设计、数据可视化和图形艺术等。

CSS 混合模式也非常实用,但它们主要用于网页设计中,帮助快速实现各种元素的混合效果。mix-blend-mode 可以将一个元素与其父元素或背景混合,而 background-blend-mode 则用于混合背景图像和背景颜色。

SVG 混合模式 <feBlend> 和 CSS 混合模式在图形处理和网页设计中各有优势。<feBlend> 提供了精细的图形混合控制,适用于复杂的矢量图形设计;而 CSS 混合模式则应用广泛,使用简便,适合快速实现网页元素的混合效果。了解并结合这两种技术的特点,可以帮助设计师和开发者选择最合适的工具,创造丰富多彩的视觉效果。

将在后续的课程中更详细地介绍 <feBlend> 的应用,敬请期待!

SVG 图形与 CSS 滤镜的结合

CSS 滤镜提供了一组用于处理元素外观的效果,比如模糊、亮度调整、对比度变化和颜色变换等。常见的 CSS 滤镜效果包括:

  • blur(): 模糊效果
  • brightness(): 亮度调整
  • contrast(): 对比度调整
  • grayscale(): 灰度效果
  • sepia(): 棕褐色效果
  • hue-rotate(): 色相旋转
  • saturate(): 饱和度调整

与 CSS 混合模式相似,它也可以与 SVG 图形结合使用,它们的结合同样可以创造出丰富多样、令人惊叹的视觉效果。也就是说,将 SVG 图形与 CSS 滤镜结合使用,能够带来许多优势和灵活性:

  • 实时动态效果:可以使用 CSS 过渡和动画来动态改变滤镜效果,从而实现实时的交互效果。
  • 精细控制:CSS 滤镜提供了像素级的控制,结合 SVG 的矢量特性,可以实现非常细腻的图形处理效果。
  • 简单易用:CSS 滤镜的语法简单明了,容易上手,并且可以与现有的 CSS 代码无缝结合。

通过将两者结合使用,设计师和开发者能够更灵活地控制图形外观,实现多种复杂的图形处理效果。

尽管如此,与 SVG 的本身具备的滤镜功能相比,CSS 滤镜要逊色许多。可以说,CSS 滤镜只是 SVG 滤镜的一种简便使用方式,或者说是其阉割版。这也是在设计课程时,没有将 CSS 滤镜与 SVG 结合使用单独列为一节课探讨的主要原因。

小结

通过前面的真实案例,你应该已经感受到了 CSS 混合模式对 SVG 图形带来的色彩增添。这种结合使用是现代 Web 设计中实现丰富视觉效果和提升用户体验的关键技术。它们一起使用有很多好处:

  • 创造独特的视觉效果:结合 SVG 图形和 CSS 混合模式可以实现其他技术无法做到的视觉效果。例如,将 SVG 图形与 mix-blend-modebackground-blend-mode 结合,可以实现多层次、丰富多彩的动态效果。
  • 增强互动性:通过 CSS 混合模式和 SVG 动态修改,可以使用户体验更加有趣。比如,当鼠标悬停时改变混合模式,或者点击时触发 SVG 动画等。
  • 提高性能:SVG 图形的矢量特性和 CSS 混合模式的硬件加速支持,可以在保证高质量视觉效果的同时,保持较高的渲染性能和响应速度。

总之,SVG 图形与 CSS 混合模式的结合使用为网页设计提供了强大的工具和无限的创意空间。合理利用 SVG 的精细控制和可缩放特性,再加上 CSS 混合模式的丰富色彩效果,设计师和开发者可以打造出视觉效果出众、互动性强、性能优越的网页应用,从而大大提升用户体验和吸引力。