現代 CSS 高階技巧,不規則邊框解決方案
本文是 CSS Houdini 之 CSS Painting API 系列第四篇。
- 現代 CSS 之高階圖片漸隱消失術[1]
- 現代 CSS 高階技巧,像 Canvas 一樣自由繪圖構建樣式![2]
- 現代 CSS 高階技巧,完美的波浪進度條效果![3]
在上三篇中,我們詳細介紹了 CSS Painting API 是如何一步一步,實現自定義圖案甚至實現動畫效果的!
在這一篇中,我們將繼續探索,嘗試使用 CSS Painting API,去實現過往 CSS 中非常難以實現的一個點,那就是如何繪制不規則圖形的邊框。
CSS Painting API
再簡單快速的過一下,什么是 CSS Painting API。
CSS Painting API 是 CSS Houdini 的一部分。而 Houdini 是一組底層 API,它們公開了 CSS 引擎的各個部分,從而使開發人員能夠通過加入瀏覽器渲染引擎的樣式和布局過程來擴展 CSS。Houdini 是一組 API,它們使開發人員可以直接訪問 CSS 對象模型[4] (CSSOM),使開發人員可以編寫瀏覽器可以解析為 CSS 的代碼,從而創建新的 CSS 功能,而無需等待它們在瀏覽器中本地實現。
CSS Paint API 目前的版本是 CSS Painting API Level 1[5]。它也被稱為 CSS Custom Paint 或者 Houdini's Paint Worklet。
我們可以把它理解為 JS In CSS,利用 JavaScript Canvas 畫布的強大能力,實現過往 CSS 無法實現的功能。
過往 CSS 實現不規則圖形的邊框方式
CSS 實現不規則圖形的邊框,一直是 CSS 的一個難點之一。在過往,雖然我們有很多方式利用 Hack 出不規則圖形的邊框,我在之前的多篇文章中有反復提及過:
我們來看看這樣一個圖形:
利用 CSS 實現這樣一個圖形是相對簡單的,可以利用 mask 或者 background 中的漸變實現,像是這樣:
但是,如果,要實現這個圖形,但是只有一層邊框,利用 CSS 就不那么好實現了,像是這樣:
在過往,有兩種相對還不錯的方式,去實現這樣一個不規則圖形的邊框:
- 借助 filter,利用多重drop-shadow()。
- 借助 SVG 濾鏡實現。
我們快速回顧一下這兩個方法。
借助 filter,利用多重 drop-shadow() 實現不規則邊框
還是上面的圖形,我們利用多重 drop-shadow(),可以大致的得到它的邊框效果。代碼如下:
可以看到,這里我們通過疊加 3 層 drop-shadow(),來實現不規則圖形的邊框,雖然 drop-shadow() 是用于生成陰影的,但是多層值很小的陰影疊加下,竟然有了類似于邊框的效果:
借助 SVG 濾鏡實現實現不規則邊框
另外一種方式,需要掌握比較深的 SVG 濾鏡知識。通過實現一種特殊的 SVG 濾鏡,再通過 CSS 的 filter 引入,實現不規則邊框。
看看代碼:
簡單淺析一下這段 SVG 濾鏡代碼:
- <feMorphology in="SourceAlpha" result="DILATED" operator="dilate" radius="1"></feMorphology> 將原圖的不透明部分作為輸入,采用了 dilate 擴張模式且程度為 radius="1",生成了一個比原圖大 1px 的黑色圖塊。
- 使用 feMerge 將黑色圖塊和原圖疊加在一起。
- 可以通過控制濾鏡中的 radius="1" 來控制邊框的大小。
這樣,也可以實現不規則圖形的邊框效果:
CodePen Demo -- 3 ways to achieve unregular border[8]。
利用 CSS Painting API 實現不規則邊框
那么,到了今天,利用 CSS Painting API ,我們有了一種更為直接的方式,更好的解決這個問題。
還是上面的圖形,我們利用 clip-path 來實現一下。
我們可以得到這樣一個圖形:
當然,本文的主角是 CSS Painting API,既然我們有 clip-path 的參數,其實完全也可以利用 CSS Painting API 的 borderDraw 來繪制這個圖形。
我們嘗試一下,改造我們的代碼:
這里,我們將原本的 clip-path 的具體路徑參數,定義為了一個 CSS 變量 --clipPath,傳入我們要實現的 borderDraw 方法中。整個圖形效果,就是要利用 background: paint(borderDraw) 繪制出來。
接下來,看看,我們需要實現 borderDraw。核心的點在于,我們通過拿到 --clipPath 參數,解析它,然后通過循環函數利用畫布把這個圖形繪制出來。
簡單解釋一下上述的代碼,注意其中最難理解的 parseClipPath() 方法的解釋。
- 首先我們,通過properties.get("--clipPath"),我們能夠拿到傳入的 --clipPath 參數。
- 通過spilt() 方法,將 --clipPath 分成一段段,也就是我們的圖形實際的繪制步驟。
- 這里有一點非常重要,也就是parseClipPath() 方法,由于我們的 -clipPath 的每一段可能是 100% 50% 這樣的構造,但是實際在繪圖的過程中,我們需要的實際坐標的絕對值,譬如在一個 100 x 100 的畫布上,我們需要將 50% 50% 的百分比坐標,轉化為實際的 50 50 這樣的絕對值。
- 在理解了parseClipPath() 后,剩下的就都非常好理解了,我們通過 ctx.beginPath()、ctx.move、ctx.lineTo 以及 ctx.closePath() 將整個 --clipPath 的圖形繪制出來。
- 最后,利用ctx.fill() 給圖形上色。
這樣,我們就得到了這樣一個圖形:
都拿到了完整的圖形了,那么我們只給這個圖形繪制邊框,不上色,不就得到了它的邊框效果了嗎?
簡單改造一些 JavaScript 代碼的最后部分:
這樣,我們就得到了圖形的邊框效果:
僅僅利用 background 繪制的缺陷
但是,僅僅利用 [bacg](background: paint(borderDraw "bacg")) 來繪制邊框效果,會有一些問題。
上述的圖形,我們僅僅賦予了 1px 的邊框,如果我們把邊框改成 5px 呢?看看會發生什么?
此時,整個圖形會變成:
可以看到,沒有展示完整的 5px 的邊框,這是由于整個畫布只有元素的高寬大小,而上述的代碼中,元素的邊框有一部分繪制到了畫布之外,因此,整個圖形并非我們期待的效果。
因此,我們需要換一種思路解決這個問題,繼續改造一下我們的代碼,僅僅需要改造 CSS 代碼即可:
這里,我們的元素本身,還是利用了 clip-path: polygon(var(--clipPath)) 剪切了自身,同時,我們借助了一個偽元素,利用這個偽元素去實現具體的邊框效果。
這里其實用了一種內外切割的思想,去實現的邊框效果:
- 利用父元素的clip-path: polygon(var(--clipPath)) 剪切掉外圍的圖形。
- 利用給偽元素的 mask 作用實際的paint(borderDraw) 方法,把圖形的內部鏤空,只保留邊框部分。
還是設置 ctx.lineWidth = 5,再看看效果:
看上去不錯,但是實際上,雖然設置了 5px 的邊框寬度,但是實際上,上圖的邊框寬度只有 2.5px 的,這是由于另外一點一半邊框實際上被切割掉了。
因此,我們如果需要實現 5px 的效果,實際上需要 ctx.lineWidth =10。
當然,我們可以通過一個 CSS 變量來控制邊框的大小:
在實際的 borderDraw 函數中,我們將傳入的 --borderWidth 參數,乘以 2 使用就好:
這樣,我們每次都能得到我們想要的邊框長度:
CodePen Demo -- CSS Hudini & Unregular Custom Border[9]。
到這里,整個實現就完成了,整個過程其實有多處非常關鍵的點,會有一點點難以理解,具體可能需要自己實際調試一遍找到實現的原理。
具體應用
在掌握了上述的方法后,我們就可以利用這個方式,實現各類不規則圖形的邊框效果,我們只需要傳入對于的 ??clip-path?
? 參數以及我們想要的邊框長度即可。
好,這樣,我們就能實現各類不同的不規則圖形的邊框效果了。
像是這樣:
得到不同圖形的邊框效果:
CodePen Demo -- CSS Hudini & Unregular Custom Border[10]。
又或者是基于它們,去實現各類按鈕效果,這種效果在以往使用 CSS 是非常非常難實現的:
它們的核心原理都是一樣的,甚至加上 Hover 效果,也是非常的輕松:
完整的代碼,你可以戳這里:CodePen Demo -- https://codepen.io/Chokcoco/pen/ExRLqdO[11]。
至此,我們再一次利用 CSS Painting API 實現了我們過往 CSS 完全無法實現的效果。這個也就是 CSS Houdini 的魅力,是 JS In CSS 的魅力。
兼容性?
好吧,其實上一篇文章也談到了兼容問題,因為可能有很多看到本篇文章并沒有去翻看前兩篇文章的同學。那么,CSS Painting API 的兼容性到底如何呢?
CanIUse - registerPaint[12] 數據如下(截止至 2022-11-23):
Chrome 和 Edge 基于 Chromium[13] 內核的瀏覽器很早就已經支持,而主流瀏覽器中,Firefox 和 Safari 目前還不支持。
CSS Houdini 雖然強大,目前看來要想大規模上生產環境,仍需一段時間的等待。讓我們給時間一點時間!
最后
好了,本文到此結束,希望本文對你有所幫助 :)
參考資料
[1]現代 CSS 之高階圖片漸隱消失術: https://juejin.cn/post/7167160342101884935?。
[2]現代 CSS 高階技巧,像 Canvas 一樣自由繪圖構建樣式!: https://juejin.cn/post/7168984450230353950?。
[3]現代 CSS 高階技巧,完美的波浪進度條效果!: https://juejin.cn/post/7170868201645932551?。
[4]CSS 對象模型: https://developer.mozilla.org/zh-CN/docs/Web/API/CSS_Object_Model?。
[5]CSS Painting API Level 1: https://drafts.css-houdini.org/css-paint-api/#paintworkletglobalscope?。
[6]有意思!不規則邊框的生成方案: https://github.com/chokcoco/iCSS/issues/106?。
[7]CSS 奇技淫巧 | 巧妙實現文字二次加粗再加邊框: https://github.com/chokcoco/iCSS/issues/145?。
[8]CodePen Demo -- 3 ways to achieve unregular border: https://codepen.io/Chokcoco/pen/oNyyNQd?。
[9]CodePen Demo -- CSS Hudini & Unregular Custom Border: https://codepen.io/Chokcoco/pen/MWXXYgJ?。
[10]CodePen Demo -- CSS Hudini & Unregular Custom Border: https://codepen.io/Chokcoco/pen/KKeROeX?。
[11]CodePen Demo -- https://codepen.io/Chokcoco/pen/ExRLqdO: https://codepen.io/Chokcoco/pen/ExRLqdO?。
[12]CanIUse - registerPaint: https://caniuse.com/?search=registerPaint?。
[13]Chromium: https://www.google.com.hk/search?newwindow=1&rlz=1C5GCEM_enCN988CN988&q=Chromium&spell=1&sa=X&ved=2ahUKEwi3he2ensL7AhVaSmwGHdnzBxgQkeECKAB6BAgoEAE?。