首页/文章/javascript

如何使用CSS混合模式和SVG来动态更改产品图片的颜色

2024-07-15
16576 分钟
...

今天跟大家分享一个与业务场景紧密结合的案例,即动态更改产品图片的颜色,它非常适用于诸如美妆的应用中。我曾在《现代 CSS》小册的《CSS 图像处理与特效指南》和《深入浅出 SVG》小册的《实战篇:SVG 与 Web 开发之使用 CSS 混合模式增强 SVG 图形》中介绍了多种不同方式实现动态更改产品图片颜色的技术方案。今天,在这里,我将向大家分享如何使用 CSS 混合模式和 SVG 来实现。如果感兴趣,请继续往下阅读:

使用CSS混合模式和SVG来改变沙发颜色案例

我们接下来要实现的效果如下:

Demo 地址:codepen.io/kylewetton/…

是不是很有意思。其实在实际场景中也有类似的一些效果,比如一些美妆应用:

你需要具备的基础知识

如果希望顺利的实现上面示例的效果,那么需要具备一点点基础知识。比如CSS的混合模SVG等。使用CSS的混合模式不同的属性值,我们可以轻易的改变一张图片的效果:

CSS混合模式还能实现很多其他的效果,这里就不阐述了。除此之外,你还需要会点扣图的技巧。不过这一点,我想对于前端来说应该不是难题。

如何实现给沙发换肤

接下来,我们就实战一下。该案例非常简单,在HTML中有三个部分:

  • 有一坨SVG代码,看上去密密麻麻,对于不了解SVG的同学肯定会带来一种恐惧感(亲,莫慌)

  • 有一张图片

  • 有几个控件,可以让用户选择颜色

简单的分析一下,你看到的一坨SVG代码:

<svg id="js-couch" class="couch__overlay" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="none" width="1000" height="394">
    <defs>
        <path d="M996.35 77.55q-1.85-1.95-8.65-3.75l-62.4-17.1q-9.3-2.75-12.15-2.5-1.8.15-2.85.45l-.75.3q2.25-16.3 3.75-22.05 1.15-4.4 1.4-10.8.2-6.6-.7-10.85-1.25-5.65-3.1-7.8-2.95-3.35-9.65-2.7-5.95.6-39.3 1.7-38.3 1.25-39.45 1.3-10.25.5-126.75.5-5.05 0-54.2 1.3-45.8 1.25-54.05.95-19.45-.45-30.4-.7-20.2-.55-23.1-1.3-22.3-5.85-26.5 1.25-2.65 4.55-3.85 7.9-.6 1.7-.7 2.5-.65-2.2-2.05-4.55-2.75-4.65-6.45-5.2-3.85-.55-13.65-.4-7.4.1-12 .4-.4.05-18.7.9-16.55.8-19.15 1.1-3.4.4-14.6 1.1-11.3.75-13.05.65h-9.8q-8.65-.05-11.45-.4-2.85-.35-9.25-.6-6.7-.15-8.5-.25-2.7-.1-27.75-.1-25.1 0-29.6.1-92.35 1.15-99 1.65-5.15.4-20 0-15.3-.4-24.4-1.25-6.75-.6-21-1.55-12.95-.9-14.85-1.1-6.45-1.05-11.05-1.5-8.7-.85-12.85.5-5.45 1.75-8.1 4.65-3.2 3.4-2.9 8.6.25 4.65 2.1 11.8 1 3.8 2.55 9.1 1 3.85 2.35 10.1-.1 1-1.5 1-1.75 0-7.7.85-7.1 1-9.8 2.05-2.4.9-23 4.75-21.2 3.9-22.05 4.15-8.2 1.85-15.05 3.35Q7.4 69.1 5.65 70.3 2.5 72.45 2 73.1.6 75 .75 79.2q.15 4.15 1.3 12.75.9 6.85 1.45 10 .5 2.75 8.55 54 6.65 42.15 7.35 46.85 1.15 7.65 4.9 28.55 4.55 25.2 6.35 31.2 2.45 8.15 3.8 11.75 1.85 4.9 3.2 5.75 1.25.8 6.85.65 2.75-.05 5.3-.25l23.85.35q.1 0 1 .95t2 .95q1.9 0 3.4-1.4l23.1-.25 43.65.4q135.05 2.15 137.9 1.9 1.25-.1 72.9.5 72.45.65 76.85.45 8.1-.35 64 .4 143.35.95 146 1.1.55.05 75.3.3 74.7.3 79.8.6 8.65.5 68.25-.35l51.75.5 1.6.4q1.95.35 3.8.05 1.45-.25 3.5-.2 1.9 0 3.35-.3 2.1-.45 8.25-.8 6.25-.3 8.75-.05 1.7.2 8 1 5.75.3 7.4-1.75 1.75-2.2 4.95-10.85 2.8-7.55 4.05-12.4.65-2.5 3.6-17.2 2.75-13.75 3.15-14.8.45-1.25 4.45-22.85 4.05-22.4 4.4-24.4.3-1.45 3.75-25.2 3.35-23.2 4-26.3 1.15-5.5 2.35-18.8 1.4-15.7.8-23.7-.6-8.35-3.35-11.15z" id="a" />
    </defs>
    <use xlink:href="#a"/>
</svg>

你不用太担心path元素中的那些数字,上面的SVG代码实际的效果如下:

第二部分有一张原图(也就是所说的产品图):

第三部分是用于控制颜色的一些控件:

<div class="colours">
    <div class="colours__labels">
        <span>Sofa</span>
        <span>Gradient</span>
        <span></span>
    </div>
    <div class="colours__inputs">
        <input id="js-color-1" class="jscolor {onFineChange:'updateCouch(this)'}" value="FCFF4D">
        <input id="js-color-2" class="jscolor {onFineChange:'updateBackgroundD(this)'}" value="24243e">
        <input id="js-color-3" class="jscolor {onFineChange:'updateBackgroundL(this)'}" value="302b63">
        <button class="gen-random" onclick="generateRandom()">Generate random</button>
    </div>
</div>

那究竟发生了什么呢?

简单的说, <img> 上面盖了一个 <svg> 元素,并且在希望改变颜色的部分使用SVG的 path 绘制出来一个矢量形状。你只需要更改 path 元素的 fill 值(填充色),并使用CSS的 mix-blend-mode:multiply 将该颜色上色到图像上

使用图像编辑软件描出所需路径

假设你的产品和示例一样,是一张沙发,那么可以直接下载下图到你本地:

然后使用图像编辑软件(我使用的是Sketch软件)打开这张图片:

使用矢量工具根据沙发的边缘,像下面这样进行描图:

现在这样的描边效果有点粗糙,可以增加描点来细化:

这可是细活。

描好之后,用颜色来填充,设置一些参数就可以达到:

把SVG拷贝出来:

拷贝出来的SVG代码如下:

<svg width="1000px" height="285px" viewBox="0 0 1000 285" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
    <g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
        <path d="M92.359375,15.1601563 C91.5665016,9.75036369 97.6468644,4.55538452 109.370117,4.46289063 C122.16228,4.36196327 141.943594,6.59233589 161.163086,7.42089844 C197.996094,9.00878906 206.387695,8.79101563 233.302734,8.79101563 C244.47692,8.79101563 262.072665,7.43774646 278.09082,7.42089844 C307.797392,7.38965284 337.814716,7.42089844 352.157227,7.42089844 C373.001805,7.42089844 393.757447,7.87557813 414.616211,7.42089844 C431.647409,7.0496521 446.838118,6.33630832 459.914063,5.78125 C478.244201,5.00315736 487.394531,3.11914063 494.027344,5.78125 C500.660156,8.44335938 501.632813,13.6982422 503.053711,13.6982422 C504.474609,13.6982422 504.904297,0.217773438 515.746094,0.217773438 C524.55517,0.217773438 531.708576,3.46640125 560.296875,4.46289063 C566.893711,4.69283354 574.527862,5.78125 583.591797,5.78125 C620.296888,5.78125 654.356171,3.85804874 690.652344,3.11914063 C702.16134,2.88484345 717.608438,3.11914063 729.430664,3.11914063 C742.116211,3.11914063 778.749023,3.11914063 794.581055,3.11914063 C810.413086,3.11914063 836.683429,2.23951661 864.150391,1.56445313 C887.247123,0.996798022 905.637349,1.20954108 907.589844,1.56445313 C923.535156,4.46289063 905.439453,50.9443359 907.589844,53.234375 C909.740234,55.5244141 914.932617,52.2695313 923.547852,55.5244141 C932.163086,58.7792969 947.413933,61.6575642 960.662109,65.3310547 C979.44094,70.5381 999,76.2663676 999,79.2919922 C999,82.2007095 999.54016,112.827542 994.683594,148.322266 C991.88052,168.808823 985.496846,191.792104 981.550781,212.458008 C974.126665,251.338788 965.831581,284.948509 960.662109,284.948509 C947.984375,284.948509 48.4257813,285.09082 36.7978516,277.898438 C25.1699219,270.706055 0.502693332,85.9480599 0.2763156,75.953125 C0.0578535434,65.9581901 18.4550781,64.6064453 36.7978516,61.3828125 C45.9721054,59.7704922 56.3347295,57.2107657 66.1601562,55.5244141 C78.6112562,53.3874145 80.1865234,51.7539062 92.359375,50.1416016 C104.532227,48.5292969 97.7890625,52.2070313 92.359375,15.1601563 Z" id="路径" fill="#0B0808"></path>
    </g>
</svg>

也可以把SVG导出来:

现在的代码有点脏,我们可以对SVG代码进行优化

使用SVGOMG优化SVG代码

这里使用在线工具SVGOMG来优化我们拷贝出来的SVG代码

URL: jakearchibald.github.io/svgomg/

点击“Open SVG”导入刚下载下来的.svg文件:

调整右侧面板参数来优化SVG代码:

优化后的SVG代码会要更干净的多:

<svg xmlns="http://www.w3.org/2000/svg" width="1000" height="285">
    <path fill="#0B0808" fill-rule="evenodd" d="M92.359375 15.1601563c-.7928734-5.40979261 5.2874894-10.60477178 17.010742-10.69726567 12.792163-.10092736 32.573477 2.12944526 51.792969 2.95800781 36.833008 1.58789062 45.224609 1.37011719 72.139648 1.37011719 11.174186 0 28.769931-1.35326917 44.788086-1.37011719 29.706572-.0312456 59.723896 0 74.066407 0 20.844578 0 41.60022.45467969 62.458984 0 17.031198-.37124634 32.221907-1.08459012 45.297852-1.63964844 18.330138-.77809264 27.480468-2.66210937 34.113281 0 6.632812 2.66210938 7.605469 7.9169922 9.026367 7.9169922 1.420898 0 1.850586-13.48046876 12.692383-13.48046876 8.809076 0 15.962482 3.24862781 44.550781 4.24511719 6.596836.22994291 14.230987 1.31835937 23.294922 1.31835937 36.705091 0 70.764374-1.92320126 107.060547-2.66210937 11.508996-.23429718 26.956094 0 38.77832 0h65.150391c15.832031 0 42.102374-.87962402 69.569336-1.5546875 23.096732-.5676551 41.486958-.35491205 43.439453 0 15.945312 2.8984375-2.150391 49.37988277 0 51.66992187 2.15039 2.2900391 7.342773-.9648437 15.958008 2.2900391 8.615234 3.2548828 23.866081 6.1331501 37.114257 9.8066406C979.44094 70.5381 999 76.2663676 999 79.2919922c0 2.9087173.54016 33.5355498-4.316406 69.0302738-2.803074 20.486557-9.186748 43.469838-13.132813 64.135742-7.424116 38.88078-15.7192 72.490501-20.888672 72.490501-12.677734 0-912.2363277.142311-923.8642574-7.050071C25.1699219 270.706055.50269333 85.9480599.2763156 75.953125c-.21846206-9.9949349 18.1787625-11.3466797 36.521536-14.5703125 9.1742538-1.6123203 19.5368779-4.1720468 29.3623046-5.8583984 12.4511-2.1369996 14.0263672-3.7705079 26.1992188-5.3828125 12.172852-1.6123047 5.4296875 2.0654297 0-34.9814453z"/>
</svg>

可以和前面未优化的代码做个对比。

把优化后的代码要是放到Codepen上的话,大至看到的效果就如下:

Demo 地址:codepen.io/airen/full/…

如果你感兴趣的话,可以按上面的操作步骤,将沙发换成你想要的图片。

把图片引入进来

按下面的模式,将图片引入进来,并添加一些CSS代码,让SVG盖在图片之上:

<div class="container">
    <svg xmlns="http://www.w3.org/2000/svg" width="1000" height="285" preserveAspectRatio="none">
        <defs>
            <path fill="var(--fillColor)" fill-rule="evenodd" d="M92.359375 15.1601563c-.7928734-5.40979261 5.2874894-10.60477178 17.010742-10.69726567 12.792163-.10092736 32.573477 2.12944526 51.792969 2.95800781 36.833008 1.58789062 45.224609 1.37011719 72.139648 1.37011719 11.174186 0 28.769931-1.35326917 44.788086-1.37011719 29.706572-.0312456 59.723896 0 74.066407 0 20.844578 0 41.60022.45467969 62.458984 0 17.031198-.37124634 32.221907-1.08459012 45.297852-1.63964844 18.330138-.77809264 27.480468-2.66210937 34.113281 0 6.632812 2.66210938 7.605469 7.9169922 9.026367 7.9169922 1.420898 0 1.850586-13.48046876 12.692383-13.48046876 8.809076 0 15.962482 3.24862781 44.550781 4.24511719 6.596836.22994291 14.230987 1.31835937 23.294922 1.31835937 36.705091 0 70.764374-1.92320126 107.060547-2.66210937 11.508996-.23429718 26.956094 0 38.77832 0h65.150391c15.832031 0 42.102374-.87962402 69.569336-1.5546875 23.096732-.5676551 41.486958-.35491205 43.439453 0 15.945312 2.8984375-2.150391 49.37988277 0 51.66992187 2.15039 2.2900391 7.342773-.9648437 15.958008 2.2900391 8.615234 3.2548828 23.866081 6.1331501 37.114257 9.8066406C979.44094 70.5381 999 76.2663676 999 79.2919922c0 2.9087173.54016 33.5355498-4.316406 69.0302738-2.803074 20.486557-9.186748 43.469838-13.132813 64.135742-7.424116 38.88078-15.7192 72.490501-20.888672 72.490501-12.677734 0-912.2363277.142311-923.8642574-7.050071C25.1699219 270.706055.50269333 85.9480599.2763156 75.953125c-.21846206-9.9949349 18.1787625-11.3466797 36.521536-14.5703125 9.1742538-1.6123203 19.5368779-4.1720468 29.3623046-5.8583984 12.4511-2.1369996 14.0263672-3.7705079 26.1992188-5.3828125 12.172852-1.6123047 5.4296875 2.0654297 0-34.9814453z" id="a"/>
        </defs>
        <use href="#a"/>
    </svg>
    <img src="/pathto/couch-8_xrckc2.png" />
</div>

注意,上面的代码把path放在了defs元素中,同时给path显式设置了一个id值为a,并且使用usehref="#a"引用path。另外,借助CSS自定义属性,让后面的事情变得简单一点。

  • pathfill中使用var()引用声明好的自定义属性--fillColor

  • 在CSS中:root{}选择器显式声明--fillColor的值,比如red

这样可以非常容易的修改path的填充色:

:root {
    --fillColor: red;
}

.container {
    position: relative;
    transform: scale(.66);
    svg {
        position: absolute;
        top: 0;
        left: 0;
    }
}

改变--fillColor的值,就能改变path的填充色:

离目标越来越近了。

在SVG路径上添加CSS混合模式

接着我们在SVG上添加CSS的混合模式相关的样式:

svg {
    mix-blend-mode: multiply;
}

这里使用了mix-blend-mode属性,设置其属性值为multiply。接着你再修改--fillColor的值时,沙发的颜色也会跟着修改:

上面看到的效果是手动的修改--fillColor的值。我们可以在页面中添加一个控件,可以让事情变得简单的多

const inputEle = document.querySelector('input')

document.documentElement.style.setProperty('--fillColor', inputEle.value);

inputEle.addEventListener('change', (e)=>{
    document.documentElement.style.setProperty('--fillColor', e.target.value);
})

你将看到的效果如下:

Demo 地址:codepen.io/airen/full/…

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

Demo 地址:codepen.io/airen/full/…

使用类似的原理,你还可以实现更复杂的效果:

Demo 地址:codepen.io/airen/full/…

看到上面的效果,是否有点成就感。感兴趣的话,不妨尝试一下。

小结

实现该效果主要运用了 CSS的混合模式mix-blend-mode: multiply;SVG的path来扣出符合你需要的填充物。并且将SVG盖在你的产品图片上(注意,定位很重要,如果不吻合,效果就会不佳)。另外还采用了CSS的自定义属性,较好的来控制颜色的更改。是不是很简单,你掌握了?


如果您觉得这篇文章有帮助,请点个赞吧~

分享文章

相关文章

更多文章 →
javascript2026-02-24
navigator.sendBeacon全指南
在前端开发中,埋点系统是必不可少的一环。我们经常需要在用户 关闭页面 、 刷新 或 跳转路由 时,向服务器发送最后一条统计数据(比如用户停留时长、页面跳出率)。 但这看似简单的需求,在实现时却危机四伏:请求发不出去?页面跳转卡顿?今天我们就来聊聊这个问题的终极解决方案 —— 。 一、 痛点与传统方案的挣扎 场景还原 当用户点击关闭按钮时,浏览器会触发生命周期事件( 或 )。如果我们直接使用普通的异步 AJAX ( 或 ) 发送请求,浏览...
学习
javascript2025-11-02
理解浏览器事件系统,从用户点击到事件对象的完整旅程
深入理解浏览器事件系统:从用户点击到事件对象的完整旅程 “当我点击页面按钮时,背后发生了什么?为什么回调函数能收到一个包含丰富信息的event对象?今天,让我们一起揭开浏览器事件系统的神秘面纱。” 一个令人困惑的现象 作为前端开发者,我们每天都在写这样的代码: 这段代码如此熟悉,以至于我们很少停下来思考:​ ​这个 对象到底从哪里来?它为什么能知道点击的精确坐标?为什么能识别是哪个元素被点击了?​ ​ 更神奇的是,当我们手动创建事件时:...
学习
javascript2025-10-01
实现大文件上传全流程详解
在日常开发中,大文件上传是个绕不开的坎——动辄几百 MB 甚至 GB 级的文件,直接上传不仅容易超时,还会让用户体验大打折扣。最近我用 Vue+Express 实现了一套完整的大文件上传方案,支持分片上传、断点续传、秒传和手动中。 一、先看效果:我们要实现什么? 先上核心功能清单,确保大家明确目标,知道我们要解决哪些实际问题: 大文件分片上传 :将文件切成固定大小的小片段分批上传,避免单次请求超时 秒传 :服务器已存在完整文件时,直接返...
学习
javascript2025-09-18
JavaScript 的多线程能力:Worker
如果你写过一些计算量稍大的 JavaScript 代码,比如图像处理、大量数据排序或者复杂的算法,你几乎肯定遇到过浏览器“卡死”的现象。点击页面没反应,动画也停了,就像整个世界都静止了。 这就是主线程被阻塞的典型后果。因为主线程既要负责执行 JavaScript,又要负责渲染页面、响应用户操作,一旦它被繁重的计算任务占满,就无暇顾及其他,用户体验便直线下降。 这个问题的根源,正是“主线程是单线程的”。那么,如何解决呢? 答案很简单:把这...
学习面试
javascript2025-09-15
一张 8K 海报差点把首屏拖垮
你给后台管理系统加了一个「企业风采」模块,运营同学一口气上传了 200 张 8K 宣传海报。首屏直接飙到 8.3 s,LCP 红得发紫。 老板一句「能不能像朋友圈那样滑到哪看到哪?」——于是你把懒加载重新翻出来折腾了一轮。 解决方案:三条技术路线,你全踩了一遍 1\. 最偷懒:原生 一行代码就能跑,浏览器帮你搞定。 🔍 关键决策点 2020 年后现代浏览器全覆盖,IE 全军覆没。 必须写死 ,否则 CLS 会抖成 PPT。 适用场景...
学习
javascript2025-09-10
🚀 Web Worker让你的应用丝滑
🌟 引言 在日常的前端开发中,你是否遇到过这样的困扰: 大数据处理时页面卡死 :处理几万条数据时,页面直接卡成PPT,用户点击毫无反应 复杂计算阻塞UI :图片处理、数据分析等计算密集型任务让整个应用假死 文件上传/下载卡顿 :大文件操作时,其他功能完全无法使用 实时数据处理性能差 :WebSocket接收大量数据时,页面渲染严重滞后 今天分享6个Web Worker的核心技巧,让你的应用告别卡顿,用户体验丝滑如德芙! 💡 核心技巧...
学习

评论

请登录后发表评论

去登录
加载评论中...

目录