CSS躬行記(7)——合成

  在圖形編輯軟件中,可以按特定地方式處理不同圖層的合成,最新的CSS規範也引入了該功能,並提供了mix-blend-mode和background-blend-mode兩個屬性。混合模式(blending mode)是一種數學算法,可計算元素重疊部分的顏色值,目前已定義了十多種不同的混合模式。

一、元素混合

  元素混合是指將元素和其背後內容(backdrop,也叫背着物)混合,由mix-blend-mode屬性設置混合模式。元素背後既可以是另一個元素,也可以是父元素的背景,並且聲明了mix-blend-mode屬性的元素被稱為前景。注意,不同層疊上下文中的元素不能混合。接下來會對已有的混合模式逐個講解,並給出相應的計算公式,下面列出的是會用到的符號含義。

  (1)Cr:計算後的顏色值。

  (2)B:進行混合的公式。

  (3)Cs:前景中的顏色。

  (4)Cb:元素背後的顏色(backdrop color)。

1)darken

  比較Cb和Cs的顏色分量(即R、G和B),選擇較暗的顏色,即保留較小值。

B(Cb, Cs) = min(Cb, Cs)

  下面將img元素的mix-blend-mode屬性定義為darken,父元素div聲明了漸變背景。在下圖中,左側是img和div默認的混合效果,右側是使用了darken混合後的效果。

<style>
  div {
    background: linear-gradient(to right, rgb(48,129,242) 10%, rgb(255,204,0) 66%, rgb(255,102,0));
  }
  img {
    mix-blend-mode: darken;
  }
</style>
<div>
  <img src="./avatar.png" />
</div>

2)lighten

  與darken類似,但選擇較亮的顏色,即保留較大值,效果如下圖所示。

B(Cb, Cs) = max(Cb, Cs)

3)difference

  取Cb和Cs顏色分量之差的絕對值,用較淺的顏色減去較深的顏色,效果如下圖所示。

B(Cb, Cs) = | Cb - Cs |

4)exclusion

  與difference類似,但對比度更低,顏色更柔和,效果如下圖所示。

B(Cb, Cs) = Cb + Cs - 2 x Cb x Cs

5)multiply

  將Cb和Cs中的顏色分量相乘,得到較暗的顏色,效果如下圖所示。

B(Cb, Cs) = Cb x Cs

6)screen

  將Cb和Cs的顏色反轉,然後相乘,最後再反轉,效果如下圖所示。

B(Cb, Cs) = 1 - [(1 - Cb) x (1 - Cs)]

7)overlay

  當Cb的顏色比Cs的顏色深時,執行multiply渲染;當Cb的顏色比Cs的顏色淺時,執行screen渲染,效果如下圖所示。

B(Cb, Cs) = HardLight(Cs, Cb)

8)hard-light

  也是對multiply和screen的綜合應用,但判斷條件與overlay相反,效果如下圖所示。

if(Cs <= 0.5)
    B(Cb, Cs) = Multiply(Cb, 2 x Cs)
else
    B(Cb, Cs) = Screen(Cb, 2 x Cs -1)

9)soft-light

  與hard-light類似,但顏色更加柔和,效果如下圖所示。

    if(Cs <= 0.5)
        B(Cb, Cs) = Cb - (1 - 2 x Cs) x Cb x (1 - Cb)
    else
        B(Cb, Cs) = Cb + (2 x Cs - 1) x (D(Cb) - Cb)
with
    if(Cb <= 0.25)
        D(Cb) = ((16 * Cb - 12) x Cb + 4) x Cb
    else
        D(Cb) = sqrt(Cb)

 

10)color-dodge

  不改變顏色,但會將其調亮,效果如下圖所示。

if(Cb == 0)
    B(Cb, Cs) = 0
else if(Cs == 1)
    B(Cb, Cs) = 1
else
    B(Cb, Cs) = min(1, Cb / (1 - Cs))

11)color-burn

  與color-dodge的作用相反,將顏色調暗,效果如下圖所示。

if(Cb == 1)
    B(Cb, Cs) = 1
else if(Cs == 0)
    B(Cb, Cs) = 0
else
    B(Cb, Cs) = 1 - min(1, (1 - Cb) / Cs)

  接下來的四個混合模式不操作顏色分量,而是以不同的方式合併Cs和Cb的色相、飽和度、亮度和顏色,會用到幾個輔助函數,如下所示。

    Lum(C) = 0.3 x Cred + 0.59 x Cgreen + 0.11 x Cblue
    
    ClipColor(C)
        L = Lum(C)
        n = min(Cred, Cgreen, Cblue)
        x = max(Cred, Cgreen, Cblue)
        if(n < 0)
            C = L + (((C - L) * L) / (L - n))
        if(x > 1)
            C = L + (((C - L) * (1 - L)) / (x - L))
        return C
    
    SetLum(C, l)
        d = l - Lum(C)
        Cred = Cred + d
        Cgreen = Cgreen + d
        Cblue = Cblue + d
        return ClipColor(C)
        
    Sat(C) = max(Cred, Cgreen, Cblue) - min(Cred, Cgreen, Cblue)

    SetSat(C, s)
        if(Cmax > Cmin)
            Cmid = (((Cmid - Cmin) x s) / (Cmax - Cmin))
            Cmax = s
        else
            Cmid = Cmax = 0
        Cmin = 0
        return C;

12)hue

  將Cb的顏色的飽和度與亮度跟Cs中對應位置的色相合併,效果如下圖所示。

B(Cb, Cs) = SetLum(SetSat(Cs, Sat(Cb)), Lum(Cb))

13)saturation

  將Cb的顏色的色相與亮度跟Cs中對應位置的飽和度合併,效果如下圖所示。

B(Cb, Cs) = SetLum(SetSat(Cb, Sat(Cs)), Lum(Cb))

14)color

  將Cb的顏色的亮度跟Cs中對應位置的色相與飽和度合併,效果如下圖所示。

B(Cb, Cs) = SetLum(Cs, Lum(Cb))

15)luminosity

  將Cb的顏色的色相與飽和度跟Cs中對應位置的亮度合併,效果如下圖所示。

B(Cb, Cs) = SetLum(Cb, Lum(Cs))

二、背景混合

  背景混合適合一個元素包含多個背景的情況,由background-blend-mode屬性設置混合模式。當混合多個背景時,會從後往前進行混合。如果包含背景色,那麼首先由背景色與最下層的背景圖混合,其結果再與次下層的背景圖混合,以此類推。

  在下面的示例中,包含兩個div元素,都使用lighten混合,其中第二個div元素包含背景色。兩個元素的混合效果如下圖所示,左側無背景色,右側有背景色。

<style>
  div {
    background: url(./avatar.png) no-repeat center,
            url(./lake.png);
    background-size: 40% 40%, cover;
    background-blend-mode: lighten;
  }
  .color {
    background-color: #F60;
  }
</style>
<div></div>
<div class="color"></div>

  注意,background-blend-mode屬性可接收多種混合模式,用逗號分隔,樣式如下,效果如下圖所示。

div {
  background-blend-mode: lighten, hard-light;
}

三、隔離

  在合成的過程中,還可通過isolation屬性隔離混合,即讓那些元素自成一組。注意,isolation屬性需要聲明到某個容器元素中,並且不能和混合模式存在於同一個元素上。

  接下來用一個簡單的例子來演示isolation屬性的用法,首先創建HTML結構,section是img的祖先元素,div是img的父元素。

<section>
  <div>
    <img src="./avatar.png" class="blend" />
  </div>
</section>
<section>
  <div class="isolation">
    <img src="./avatar.png" class="blend" />
  </div>
</section>

  然後添加CSS樣式,將混合模式聲明在img元素上,第二個div元素定義了isolation屬性。得到的效果如下圖所示,左側的祖先元素的背景會與圖像混合,而右側因為發生了隔離,所以就不會與背景混合。

section {
  background: linear-gradient(to right, #3081F2 10%, #FC0 66%, #F60);
}
.blend {
  mix-blend-mode: lighten;
}
.isolation {
  isolation: isolate;
}

  注意,建立層疊上下文的元素可自動獨立,而不受isolation屬性的影響,能發生層疊上下文的情形包括:

  (1)文檔根元素,例如html元素。

  (2)相對或絕對定位且z-index屬性值不為auto的元素。

  (3)固定或粘滯定位的元素。

  (4)彈性盒的子元素,且z-index屬性值不為auto。

  (5)網格容器的子元素,且z-index屬性值不為auto。

  (6)opacity屬性值小於1的元素。

  (7)mix-blend-mode屬性值不為normal的元素。

  (8)transform、filter、perspective、clip-path、mask、mask-image和mask-border屬性值不為none的元素。

  (9)isolation屬性值為isolate的元素。

  (10)-webkit-overflow-scrolling屬性值為touch的元素。

  (11)contain屬性值為layout、paint或包含它們其中之一的合成值(例如strict、content)的元素。

  (12)為will-change定義任一屬性。