CSS mask 與 切圖藝術

蓦然JL發表於2024-05-06
歡迎關注我的公眾號:前端偵探

作為一名 CSSer,我並不反對“切圖”。

相反,有些地方還是更推薦的,特別是那些奇形怪狀的 UI,合理的“切圖”可以極大地提高佈局效率,當然,這裡需要一點點技巧,將“切圖”和 CSS 現有能力結合起來,一起學習一下吧

一、“切圖”的侷限性

傳統的“切圖”簡單暴力,但往往缺少適應性。

適應性一般有兩種,一是尺寸自適應,二是顏色可以自定義

舉個例子,有這樣一個優惠券樣式

image.png

關於這類樣式實現技巧,之前在這篇文章中有詳細介紹:

CSS 實現優惠券的技巧

不過這裡略微不一樣的地方是,兩個凹陷處都是平滑處理的

image.png

單純實現內凹圓角已經很費勁了,現在還來個平滑圓角?是時候祭出最後大招了--切圖。

但是,切圖真的能解決嗎?假設已經得到了這樣一張圖片

div{
  background: url(a.png) 0/100% 100%;
}

下面是這張圖片在不同尺寸下的表現

image.png

除非整個佈局的尺寸是完全固定的,否則都會有不同程度的拉伸情況,這是視覺最為忌諱的。

另外,整張圖是固定的,也無法直接修改背景色,如果有多種狀態,還需要儲存多份

.div1{
  background: url(a.png) 0/100% 100%;
}
.div2{
  background: url(b.png) 0/100% 100%;
}

有沒有更為靈活的切圖方式呢?

二、圖形運算與CSS MASK

經常會和設計稿打交道,對設計軟體(Photoshop、Figma等)應該或多或少也有所瞭解了,這裡簡單介紹一下圖形運算(也稱布林運算),通常有 4 種型別

image.png

這是一個非常常見的設計技巧,可以將不同的圖形經過運算合成新的圖形。

下面是一個經典案例,就是透過圓的布林運算繪製的

image.png

這麼好的特性,CSS 中有類似的嗎?

這就不得不提到CSS mask 了,CSS MASK 可以指定一張圖作為遮罩圖片。

div{
  mask: url(圖片);
  mask: 漸變;
}

遮罩圖片可以是圖片,也可以是漸變。主要原理是顯示遮罩圖片不透明的部分,透明的則會被裁剪,示意如下

image.png

但是,很多情況下,單一的遮罩並不能滿足需求,比如這樣一個帶缺口的圓,單一的漸變可能無法繪製

image.png

因此,我們需要用到和設計中圖形布林運算一樣的技巧。

這和 CSS mask中的mask-composite是一一對應的

/* Keyword values */
mask-composite: add; /* 疊加(預設) */
mask-composite: subtract; /* 減去,排除掉上層的區域 */
mask-composite: intersect; /* 相交,只顯示重合的地方 */
mask-composite: exclude; /* 排除,只顯示不重合的地方 */

利用這個特性,可以很輕易的合成帶缺口的圓,也就是說,我們可以使用 CSS 的方式自由的去裁剪、合成我們想要的圖形。

image.png

另外,-webkit-mask-composite與標準下的值有所不同,屬性值非常多,如下

-webkit-mask-composite: clear; /*清除,不顯示任何遮罩*/
-webkit-mask-composite: copy; /*只顯示上方遮罩,不顯示下方遮罩*/
-webkit-mask-composite: source-over; /*預設值,正常的疊加,等同於 add */
-webkit-mask-composite: source-in; /*只顯示重合的地方,等同於 intersect*/
-webkit-mask-composite: source-out; /*只顯示上方遮罩,重合的地方不顯示,等同於subtract*/
-webkit-mask-composite: source-atop; /*只顯示下方遮罩*/
-webkit-mask-composite: destination-over; /*正常疊加*/
-webkit-mask-composite: destination-in; /*只顯示重合的地方*/
-webkit-mask-composite: destination-out;/*只顯示下方遮罩,重合的地方不顯示*/
-webkit-mask-composite: destination-atop;/*只顯示上方遮罩*/
-webkit-mask-composite: xor; /*只顯示不重合的地方,等同於 exclude*/

其實是借用了 Canvas 中的圖形合成屬性,並且區分了圖層順序,但是對於遮罩層來講,其實只需要關注遮罩的形狀,不要關注圖層的顏色,所以上述的一些值效果是完全相同的。

image.png

這裡可以先不用每個都理解,知道有上面4種型別就行了,其實可以一一去試驗,等熟悉了自然就知道每個屬性的具體用途了。

下面是每個屬性的演示

https://codepen.io/xboxyan/pen/RwKbGwN

瞭解了這些,接下來看看在切圖中的應用。

三、圓滑的內凹圓角

回到前面的問題,同樣是切圖,不過我們需要換一種方式。

仔細觀察,從整體反向考慮,其實就是一個正常的圓角矩形,然後挖去兩個平滑的缺口,兩個缺口的大小是固定的,位置也是相對不變的,如圖所示黑色的部分

image.png

所以下面的問題就是,如何來得到這兩個缺口

單純CSS漸變是難以繪製的,所以這裡可以直接“切圖”。但是設計稿上給的是紅色部分,如何去手動做一個缺口部分呢?

這就稍微藉助設計工具了,這裡以 Figma 為例,我們繪製一個矩形,居右對齊,確保可以覆蓋缺口(如下,藍色部分)

image.png

然後,將藍色矩形圖層放在原圖形下面

image.png

最後,選中這兩個圖形,執行減去頂層

image.png

完成!!

image.png

用同樣的方式,可以得到左右兩個半圓,或者直接翻轉一下也行

image.png

接下來,我們需要將這兩個 svg 轉為內聯,推薦用張鑫旭老師的這個

SVG線上壓縮合並工具

最後,我們使用3層遮罩,兩個半圓加上整個矩形,透過遮罩合成(僅顯示不重疊部分)就可以得到我們需要的圖形了

image.png

這樣做的好處是兩個半圓是透過CSS定位實現的,所以可以確保一定是居中、靠近邊緣的,不會因為尺寸的變化而變形。

下面是完整程式碼

coupon{
  width: 300px;
  height: 150px;
  border-radius: 8px;
  background: linear-gradient(85deg, #FF9078 7.57%, #FA3440 80.06%);
  -webkit-mask: url("data:image/svg+xml,%3Csvg width='20' height='48' viewBox='0 0 20 48' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M0 48V0c0 2.21 1.809 3.958 3.974 4.395C13.116 6.238 20 14.315 20 24S13.116 41.762 3.974 43.605C1.81 44.042 0 45.791 0 48z' fill='%23000'/%3E%3C/svg%3E") 0 50%/20px no-repeat,
    url("data:image/svg+xml,%3Csvg width='20' height='48' viewBox='0 0 20 48' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M20 48V0c0 2.209-1.809 3.959-3.975 4.395C6.885 6.238 0 14.315 0 24s6.885 17.762 16.025 19.605C18.191 44.041 20 45.791 20 48z' fill='%23000'/%3E%3C/svg%3E") 100% 50%/20px no-repeat,
    linear-gradient(red 0 0);
  -webkit-mask-composite: xor;
}

這樣就得到了尺寸自適應、可更換顏色的優惠券樣式了

image.png

你也可以訪問線上連結

四、投票 PK 樣式

下面來看這樣一個例子

image.png

注意,這裡也是有平滑圓角的,並且在寬度改變時,傾斜角是固定的

image.png

同樣,也可以用“切圖”的方式來實現這樣的效果

在這裡,我們還是從整體考慮,將圖形補全,用圖形合成的方式,在 Figma 中繪製出如下的“平滑直三角”

image.png

然後在 CSS 中透過 mask 減去黑色部分就行了,下面是完整程式碼

.pk{
  display: flex;
  width: 400px;
}
.item{
  height: 40px;
}
.left{
  width: 50%;
  border-radius: 40px 0 0 40px;
  background: linear-gradient(85deg, #FF9078 7.57%, #FA3440 80.06%);
  -webkit-mask: linear-gradient(red,red), url("data:image/svg+xml,%3Csvg width='16' height='40' viewBox='0 0 16 40' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M16 0h-2.344a1 1 0 0 1 .957 1.287L3.855 37.148A4 4 0 0 1 .023 40H16V0z' fill='%23C6F'/%3E%3C/svg%3E") right/auto 100% no-repeat;
  -webkit-mask-composite: xor;
}
.right{
  flex: 1;
  border-radius:  0 40px 40px 0;
  background: linear-gradient(274deg, #5FB6F5 -3.81%, #4B80EE 62.98%);
  -webkit-mask: linear-gradient(red,red), url("data:image/svg+xml,%3Csvg width='16' height='40' viewBox='0 0 16 40' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M0 40h2.344a1 1 0 0 1-.957-1.287L12.145 2.85A4 4 0 0 1 15.977 0H0v40z' fill='%23C6F'/%3E%3C/svg%3E") left/auto 100% no-repeat;
  -webkit-mask-composite: xor;
}

這樣實現的傾角支援漸變,支援自適應寬度,效果如下

image.png

你也可以訪問線上連結

五、平滑 tab 樣式

最後再來看一個例子:平滑 tab 標籤,就是 Chrome 標籤欄那樣的

image.png

其實關於這個佈局,之前也專門寫過文章,介紹了5種不同方式,有興趣的可以回顧一下

CSS 實現Chrome標籤欄的技巧

這次再來介紹一個比較簡單、實用的“切圖”方式。

透過前面兩個例子,可能你已經猜到要怎麼做了。沒錯,就是先補全

image.png

然後在設計軟體中將黑色部分單獨“切”出來

image.png

然後在 CSS 中透過 mask 減去這兩部分黑色部分就行了,和第一個例子非常像,下面是完整程式碼

tab{
  line-height: 40px;
  padding: 0 30px;
  background-color:royalblue;
  color: #fff;
  -webkit-mask: url("data:image/svg+xml,%3Csvg width='28' height='40' viewBox='0 0 28 40' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M28 0H0v40h1c8.283 0 15-6.717 15-15V12c0-6.627 5.373-12 12-12z' fill='%23000'/%3E%3C/svg%3E") 0 0 no-repeat,
    url("data:image/svg+xml,%3Csvg width='28' height='40' viewBox='0 0 28 40' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M0 0h28v40h-1c-8.283 0-15-6.717-15-15V12C12 5.373 6.627 0 0 0z' fill='%23000'/%3E%3C/svg%3E") 100% 0 no-repeat,
    linear-gradient(red 0 0);
  -webkit-mask-composite: xor;
}

這樣實現的 tab 既做到了自適應寬度,也能隨意更改背景,效果如下

image.png

你也可以訪問線上連結

六、總結一下

以上就是本文的全部內容了,介紹了一種特殊的“切圖”手法,將切圖與CSS MASK結合起來,既保留了“切圖”的簡單暴力,又滿足了CSS自適應的特點,下面簡單總結一下

  1. 傳統的“切圖”簡單暴力,但往往缺少適應性:尺寸自適應和顏色自適應
  2. 圖形布林運算主要有:合併形狀、減去頂層、交叉形狀、排除重疊
  3. CSS MASK composite 剛好也有相同的特性
  4. 可以從整體反向考慮,將難以實現的部分由“切圖”完成,然後透過mask從整體減去這一部分
  5. 由於缺口部分是透過CSS定位實現,因此可以確保整體的自適應性

前端作為設計師和開發工程師之間的橋樑,很多實現都可以從設計的角度去考慮,很多複雜的效果或者佈局也是一層一層疊加而成,也算是給前端提供的一種佈局思路。最後,如果覺得還不錯,對你有幫助的話,歡迎點贊、收藏、轉發❤❤❤

相關文章