叠加的半透明方块的颜色取决于顺序吗?

89
为什么两个半透明盒子叠在一起后的最终颜色会取决于它们的顺序?
我该怎样才能使两种情况下得到相同的颜色?
.a {
  background-color: rgba(255, 0, 0, 0.5)
}

.b {
  background-color: rgba(0, 0, 255, 0.5)
}
<span class="a"><span class="b">          Color 1</span></span>
<span class="b"><span class="a">Different Color 2</span></span>


7
我不知道问题的答案,但在Photoshop中也发生了同样的事情,这是我多年来接受计算机色彩理论的一部分。我会四处寻找更多信息。 - YAHsaves
11
就它的价值而言,任何不是100%透明并且从前面照明的物体在现实生活中也会出现同样的情况。更多来自前方物体的光线到达你的眼睛,因此即使两者都有50%的透明度,前方物体的颜色对最终颜色的影响更大。 - jpa
4
@YAHsaves说:0和100的平均数是50(步骤1)。50和150的平均数是100(步骤2)。与之相比:150和0的平均数是75(步骤1)。75和100的平均数是87.5(步骤2)。问题在于这三个数字未被正确加权。需要基于所有数字同时计算平均值;不能只是逐步递归计算平均值。(请注意,平均值计算本质上是一种50%的透明度计算。对于不同的透明度级别,计算会有所变化,但原则保持不变) - Flater
2
学到的教训:使用'mix-blend-mode: multiply',我将获得与堆叠顺序无关的颜色 - 这正是我最初要寻找的!我认为@Moffens的答案对于任何面临相同问题的用户都非常有用。但是Temani Afif的解释实际上适用于我的问题,并描述了为什么HTML表现出不同的方式(它模拟半透明箔片通过物理光传播),因此绿色勾号归他所有。 - rmv
4个回答

112

由于上层的透明度会影响底层颜色,因此在两种情况下颜色的组合不同。

对于第一种情况,您可以看到顶层有50%蓝色和50%透明度。透明部分显示了底层50%红色(因此总共只能看到25%红色)。对于第二种情况,同样的逻辑适用(50%红色25%蓝色);因此,您将看到不同的颜色,因为对于两种情况,我们没有相同的比例。

要避免这种情况,您需要为两个颜色使用相同的比例。

以下是一个示例,以更好地说明和展示如何获得相同的颜色:

在顶层(内部span)中,我们有透明度0.25(因此我们有第一种颜色的25%和75%的透明度),然后对于底层(外部span),我们有透明度0.333(因此我们有75%的1/3 = 25%的颜色和其余部分为透明)。我们在两个图层中都有相同的比例(25%),因此我们看到的是相同的颜色,即使我们反转图层的顺序。

.a {
  background-color: rgba(255, 0, 0, 0.333)
}

.b {
  background-color: rgba(0, 0, 255, 0.333)
}

.a > .b {
  background-color: rgba(0, 0, 255, 0.25)
}
.b > .a {
  background-color: rgba(255, 0, 0, 0.25)
}
<span class="a"><span class="b">          Color 1</span></span>
<span class="b"><span class="a">Different Color 2</span></span>

值得一提的是,白色背景也会影响颜色的渲染。它占比为50%,这将使逻辑结果为100%(25%+25%+50%)。

您可能还注意到,如果顶层具有大于0.5的不透明度,则无法为我们两种颜色设置相同的比例,因为第一种会超过50%,而第二种仍然少于50%:

.a {
  background-color: rgba(255, 0, 0, 1) /*taking 40% even with opacity:1*/
}

.b {
  background-color: rgba(0, 0, 255, 1) /*taking 40% even with opacity:1*/
}

.a > .b {
  background-color: rgba(0, 0, 255, 0.6) /* taking 60%*/
}
.b > .a {
  background-color: rgba(255, 0, 0, 0.6) /* taking 60%*/
}
<span class="a"><span class="b">          Color 1</span></span>
<span class="b"><span class="a">Different Color 2</span></span>

常见的简单情况是顶层使用 opacity:1,这使得顶部颜色占比为100%,因此它是不透明颜色。


更准确和精确的解释需要使用以下公式来计算两个图层组合后我们看到的颜色ref

ColorF = (ColorT*opacityT + ColorB*OpacityB*(1 - OpacityT)) / factor
ColorF是我们最终的颜色。ColorT/ColorB分别是顶部和底部的颜色。opacityT/opacityB分别是为每种颜色定义的顶部和底部不透明度:

这个factor由以下公式定义:OpacityT + OpacityB*(1 - OpacityT)

很明显,如果我们交换两个层,factor将不会改变(它将保持不变),但我们可以清楚地看到每种颜色的比例会改变,因为我们没有相同的乘数。

对于我们的初始情况,两个不透明度都是0.5,所以我们将有:

ColorF = (ColorT*0.5 + ColorB*0.5*(1 - 0.5)) / factor

如上所述,顶部颜色的比例为50%(0.5),底部颜色的比例为25%(0.5 *(1-0.5)),因此切换图层也会切换这些比例;因此,我们看到了不同的最终颜色。

现在,如果我们考虑第二个示例,我们将有:

ColorF = (ColorT*0.25 + ColorB*0.333*(1 - 0.25)) / factor

在这种情况下,我们有0.25 = 0.333*(1 - 0.25),因此切换两个层将没有影响; 因此颜色将保持不变。

我们还可以清楚地识别出一些特殊情况:

  • 当顶层具有opacity:0时,公式等于ColorF = ColorB
  • 当顶层具有opacity:1时,公式等于ColorF = ColorT

28
@ChrisHappy 我并不是在解决问题,而是在解释。有时候解释比修复更好。 - Temani Afif
即使将div分离并使用position: absolute来叠放,问题仍然存在。 - YAHsaves
1
@YAHsaves 即使我们使用多个背景或任何使两种颜色重叠的东西(无论是否使用绝对位置),问题仍会出现 ;) - Temani Afif
5
如果你想添加一个TLDR(总结),那就是它们是乘法关系而不是加法关系。 - Caramiriel
2
@Caramiriel:“乘法”仍然无法解释为什么该操作不是可交换的。 - Eric Duminil
谢谢 @TemaniAfif,您提供的公式也可以用于计算给定背景(通常为 OpacityB=1.0)和给定 OpacityT 的新透明颜色: ColorT=-(((ColorF-ColorB)*OpacityB-ColorF)*OpacityT+(ColorB-ColorF)*OpacityB)/opacityT 这导致: ColorT=(ColorB*OpacityT+ColorF-ColorB)/opacityT - DrMarbuse

42

您可以使用 CSS 属性 mix-blend-mode: multiply(仅受限于一些浏览器的支持)。

.a {
  background-color: rgba(255, 0, 0, 0.5);
  mix-blend-mode: multiply;
}

.b {
  background-color: rgba(0, 0, 255, 0.5);
  mix-blend-mode: multiply;
}

.c {
  position: relative;
  left: 0px;
  width: 50px;
  height: 50px;
}

.d {
  position: relative;
  left: 25px;
  top: -50px;
  width: 50px;
  height: 50px;
}
<span class="a"><span class="b">          Color 1</span></span>
<span class="b"><span class="a">Different Color 2</span></span>

<div class="c a"></div>
<div class="d b"></div>

<div class="c b"></div>
<div class="d a"></div>


6
从这个列表中选择几乎任何交换律的颜色混合模式即可达到效果。列表链接:http://www.deepskycolors.com/archive/2010/04/21/formulas-for-Photoshop-blending-modes.html - Salman A
@Salman A:所以在HTML-CSS中的默认行为是“混合模式”=“叠加”,而不是我错误地期望的“乘法”? - rmv
3
为什么你会期望除了覆盖/正常模式之外的其他模式?如果你把一个不透明的蓝色框放在红色框前面,预期的颜色是什么?它将是蓝色而不是红色或蓝色和红色的混合。 - Salman A
1
@Moffen 完美的答案,但您能否添加一个解释,说明为什么这样做有效,以及原始代码为什么无效? - Bergi
1
@rmv: “正常”混合模式最接近的实际上是颜料混合。例如,如果你拿一些白色颜料,用红色颜料替换其中一半,然后用蓝色颜料替换所得到的粉色颜料的一半,你最终会得到一种带有蓝紫色调的颜色(25%白色,25%红色,50%蓝色)。但如果你先用蓝色颜料替换同样的白色颜料的一半,然后再用所得到的颜料的一半替换成红色颜料,你最终会得到一种带有红紫色调的颜色(25%白色,25%蓝色,50%红色)。 - Ilmari Karonen
显示剩余2条评论

25

你正在按以下顺序混合三种颜色:

  • rgba(0, 0, 255, 0.5) over (rgba(255, 0, 0, 0.5) over rgba(255, 255, 255, 1))
  • rgba(255, 0, 0, 0.5) over (rgba(0, 0, 255, 0.5) over rgba(255, 255, 255, 1))

但你得到了不同的结果。这是因为前景颜色与背景颜色使用正常混合模式1,2进行混合,而这种混合模式不具有交换律3。由于它不具备交换律,交换前景和背景颜色将产生不同的结果。

1 混合模式是一个接受前景色和背景色的函数,应用某些公式并返回结果颜色。

2 给定两种颜色,背景和前景,正常混合模式简单地返回前景色。

3 如果改变操作数的顺序不改变结果,则该运算是交换律的,例如加法是交换律的(1 + 2 = 2 + 1),而减法不是(1-2≠2-1)。

解决方案是使用具有交换律的混合模式:对于任何顺序相同的颜色对返回相同的颜色(例如乘法混合模式将两种颜色相乘并返回结果颜色;或者暗混合模式将返回两种颜色中更暗的颜色)。

$(function() {
  $("#mode").on("change", function() {
    var mode = $(this).val();
    $("#demo").find(".a, .b").css({
      "mix-blend-mode": mode
    });
  });
});
#demo > div {
  width: 12em;
  height: 5em;
  margin: 1em 0;
}

#demo > div > div {
  width: 12em;
  height: 4em;
  position: relative;
  top: .5em;
  left: 4em;
}

.a {
  background-color: rgba(255, 0, 0, 0.5);
}

.b {
  background-color: rgba(0, 0, 255, 0.5);
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>

<select id="mode">
  <optgroup label="commutative">
    <option>multiply</option>
    <option>screen</option>
    <option>darken</option>
    <option>lighten</option>
    <option>difference</option>
    <option>exclusion</option>
  </optgroup>
  <optgroup label="non-commutative">
    <option selected>normal</option>
    <option>overlay</option>
    <option>color-dodge</option>
    <option>color-burn</option>
    <option>hard-light</option>
    <option>soft-light</option>
    <option>hue</option>
    <option>saturation</option>
    <option>color</option>
    <option>luminosity</option>
  </optgroup>
</select>

<div id="demo">
  <div class="a">
    <div class="b"></div>
  </div>
  <div class="b">
    <div class="a"></div>
  </div>
</div>

为了完整起见,这是计算组合颜色的公式:

αs x (1 - αb) x Cs + αs x αb x B(Cb, Cs) + (1 - αs) x αb x Cb

使用以下参数:

Cs:前景色的颜色值
αs:前景色的 alpha 值
Cb:背景色的颜色值
αb:背景色的 alpha 值
B:混合函数


我正在尝试理解计算复合颜色的公式,就是你在最后给出的那个。我截了一张结果的屏幕截图来读取获得的颜色,并使用这个公式找到这些值,但它并不对应。你能否将此公式应用于你的示例中,我很想这样做,我已经试了几周了! :) - oceanGermanique
1
这个公式来自于 https://www.w3.org/TR/compositing-1/#blending,请看看能否找到参考资料。 - Salman A
是的,就是通过这个链接我发现了这个公式,但我从未成功地将其应用于αs和αb不等于1的示例中。 - oceanGermanique

9

要了解发生的情况,请查看Temani Afif的回答。
作为另一种解决方案,您可以使用一个标签,例如a,将其定位并在其内部降低z-index。然后堆叠顺序始终相同:第一行中b位于a的上方绘制,第二行中a位于b的下方绘制。

.a {
  background-color: rgba(255, 0, 0, 0.5);
}

.b {
  background-color: rgba(0, 0, 255, 0.5);
}

.b .a {position:relative; z-index:-1;}
<span class="a"><span class="b">     Color 1</span></span>
<span class="b"><span class="a">Same Color 2</span></span>


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接