本文是 CSS Houdini 之 CSS Painting API 系列第四篇,
- 現代 CSS 之高階圖片漸隱消失術
- 現代 CSS 高階技巧,像 Canvas 一樣自由繪圖構建樣式!
- 現代 CSS 高階技巧,完美的波浪進度條效果!
在上三篇中,我們詳細介紹了 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 物件模型 (CSSOM),使開發人員可以撰寫瀏覽器可以決議為 CSS 的代碼,從而創建新的 CSS 功能,而無需等待它們在瀏覽器中本地實作,
CSS Paint API 目前的版本是 CSS Painting API Level 1,它也被稱為 CSS Custom Paint 或者 Houdini's Paint Worklet,
我們可以把它理解為 JS In CSS,利用 JavaScript Canvas 畫布的強大能力,實作過往 CSS 無法實作的功能,
過往 CSS 實作不規則圖形的邊框方式
CSS 實作不規則圖形的邊框,一直是 CSS 的一個難點之一,在過往,雖然我們有很多方式利用 Hack 出不規則圖形的邊框,我在之前的多篇文章中有反復提及過:
- 有意思!不規則邊框的生成方案
- CSS 奇技淫巧 | 巧妙實作文字二次加粗再加邊框
我們來看看這樣一個圖形:
利用 CSS 實作這樣一個圖形是相對簡單的,可以利用 mask 或者 background 中的漸變實作,像是這樣:
<div ></div>
.arrow-button {
position: relative;
width: 180px;
height: 64px;
background: #f49714;
&::after {
content: "";
position: absolute;
width: 32px;
height: 64px;
top: 0;
right: -32px;
background:
linear-gradient(-45deg, transparent 0, transparent 22px, #f49714 22px, #f49714 100%),
linear-gradient(-135deg, transparent 0, transparent 22px, #f49714 22px, #f49714 100%);
background-size: 32px 32px;
background-repeat: no-repeat;
background-position: 0 bottom, 0 top;
}
}
但是,如果,要實作這個圖形,但是只有一層邊框,利用 CSS 就不那么好實作了,像是這樣:
在過往,有兩種相對還不錯的方式,去實作這樣一個不規則圖形的邊框:
- 借助 filter,利用多重
drop-shadow() - 借助 SVG 濾鏡實作
我們快速回顧一下這兩個方法,
借助 filter,利用多重 drop-shadow() 實作不規則邊框
還是上面的圖形,我們利用多重 drop-shadow(),可以大致的得到它的邊框效果,代碼如下:
div {
position: relative;
width: 180px;
height: 64px;
background: #fff;
&::after {
content: "";
position: absolute;
width: 32px;
height: 64px;
top: 0;
right: -32px;
background:
linear-gradient(-45deg, transparent 0, transparent 22px, #fff 22px, #fff 100%),
linear-gradient(-135deg, transparent 0, transparent 22px, #fff 22px, #fff 100%);
background-size: 32px 32px;
background-repeat: no-repeat;
background-position: 0 bottom, 0 top;
}
}
div {
filter:
drop-shadow(0px 0px .5px #000)
drop-shadow(0px 0px .5px #000)
drop-shadow(0px 0px .5px #000);
}
可以看到,這里我們通過疊加 3 層 drop-shadow(),來實作不規則圖形的邊框,雖然 drop-shadow() 是用于生成陰影的,但是多層值很小的陰影疊加下,竟然有了類似于邊框的效果:
借助 SVG 濾鏡實作實作不規則邊框
另外一種方式,需要掌握比較深的 SVG 濾鏡知識,通過實作一種特殊的 SVG 濾鏡,再通過 CSS 的 filter 引入,實作不規則邊框,
看看代碼:
<div></div>
<svg height="0">
<filter id="outline">
<feMorphology in="SourceAlpha" result="DILATED" operator="dilate" radius="1"></feMorphology>
<feMerge>
<feMergeNode in="DILATED" />
<feMergeNode in="SourceGraphic" />
</feMerge>
</filter>
</svg>
div {
position: relative;
width: 180px;
height: 64px;
background: #fff;
&::after {
content: "";
position: absolute;
width: 32px;
height: 64px;
top: 0;
right: -32px;
background:
linear-gradient(-45deg, transparent 0, transparent 22px, #fff 22px, #fff 100%),
linear-gradient(-135deg, transparent 0, transparent 22px, #fff 22px, #fff 100%);
background-size: 32px 32px;
background-repeat: no-repeat;
background-position: 0 bottom, 0 top;
}
}
div {
filter: url(#outline);
}
簡單淺析一下這段 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
利用 CSS Painting API 實作不規則邊框
那么,到了今天,利用 CSS Painting API ,我們有了一種更為直接的方式,更好的解決這個問題,
還是上面的圖形,我們利用 clip-path 來實作一下,
<div></div>
div {
position: relative;
width: 200px;
height: 64px;
background: #f49714;
clip-path: polygon(85% 0%, 100% 50%, 85% 100%, 0% 100%, 0% 0%;);
}
我們可以得到這樣一個圖形:
當然,本文的主角是 CSS Painting API,既然我們有 clip-path 的引數,其實完全也可以利用 CSS Painting API 的 borderDraw 來繪制這個圖形,
我們嘗試一下,改造我們的代碼:
<div></div>
<script>
if (CSS.paintWorklet) {
CSS.paintWorklet.addModule('/CSSHoudini.js');
}
</script>
div {
position: relative;
width: 200px;
height: 64px;
background: paint(borderDraw);
--clipPath: 85% 0%, 100% 50%, 85% 100%, 0% 100%, 0% 0%;);
}
這里,我們將原本的 clip-path 的具體路徑引數,定義為了一個 CSS 變數 --clipPath,傳入我們要實作的 borderDraw 方法中,整個圖形效果,就是要利用 background: paint(borderDraw) 繪制出來,
接下來,看看,我們需要實作 borderDraw,核心的點在于,我們通過拿到 --clipPath 引數,決議它,然后通過回圈函式利用畫布把這個圖形繪制出來,
// CSSHoudini.js 檔案
registerPaint(
"borderDraw",
class {
static get inputProperties() {
return ["--clipPath"];
}
paint(ctx, size, properties) {
const { width, height } = size;
const clipPath = properties.get("--clipPath");
const paths = clipPath.toString().split(",");
const parseClipPath = function (obj) {
const x = obj[0];
const y = obj[1];
let fx = 0,
fy = 0;
if (x.indexOf("%") > -1) {
fx = (parseFloat(x) / 100) * width;
} else if (x.indexOf("px") > -1) {
fx = parseFloat(x);
}
if (y.indexOf("%") > -1) {
fy = (parseFloat(y) / 100) * height;
} else if (y.indexOf("px") > -1) {
fy = parseFloat(y);
}
return [fx, fy];
};
var p = parseClipPath(paths[0].trim().split(" "));
ctx.beginPath();
ctx.moveTo(p[0], p[1]);
for (var i = 1; i < paths.length; i++) {
p = parseClipPath(paths[i].trim().split(" "));
ctx.lineTo(p[0], p[1]);
}
ctx.closePath();
ctx.fill();
}
}
);
簡單解釋一下上述的代碼,注意其中最難理解的 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 代碼的最后部分:
// CSSHoudini.js 檔案
registerPaint(
"borderDraw",
class {
static get inputProperties() {
return ["--clipPath"];
}
paint(ctx, size, properties) {
// ...
ctx.closePath();
// ctx.fill();
ctx.lineWidth = 1;
ctx.strokeStyle = "#000";
ctx.stroke();
}
}
);
這樣,我們就得到了圖形的邊框效果:
僅僅利用 background 繪制的缺陷
但是,僅僅利用 [bacg](background: paint(borderDraw)) 來繪制邊框效果,會有一些問題,
上述的圖形,我們僅僅賦予了 1px 的邊框,如果我們把邊框改成 5px 呢?看看會發生什么?
// CSSHoudini.js 檔案
registerPaint(
"borderDraw",
class {
static get inputProperties() {
return ["--clipPath"];
}
paint(ctx, size, properties) {
// ...
ctx.lineWidth = 5;
ctx.strokeStyle = "#000";
ctx.stroke();
}
}
);
此時,整個圖形會變成:
可以看到,沒有展示完整的 5px 的邊框,這是由于整個畫布只有元素的高寬大小,而上述的代碼中,元素的邊框有一部分繪制到了畫布之外,因此,整個圖形并非我們期待的效果,
因此,我們需要換一種思路解決這個問題,繼續改造一下我們的代碼,僅僅需要改造 CSS 代碼即可:
div {
position: relative;
width: 200px;
height: 64px;
margin: auto;
clip-path: polygon(var(--clipPath));
--clipPath: 85% 0%, 100% 50%, 85% 100%, 0% 100%, 0% 0%;
&::before {
content:"";
position:absolute;
inset: 0;
mask: paint(borderDraw);
background: #000;
}
}
這里,我們的元素本身,還是利用了 clip-path: polygon(var(--clipPath)) 剪切了自身,同時,我們借助了一個偽元素,利用這個偽元素去實作具體的邊框效果,
這里其實用了一種內外切割的思想,去實作的邊框效果:
- 利用父元素的
clip-path: polygon(var(--clipPath))剪切掉外圍的圖形 - 利用給偽元素的 mask 作用實際的
paint(borderDraw)方法,把圖形的內部鏤空,只保留邊框部分
還是設定 ctx.lineWidth = 5,再看看效果:
看上去不錯,但是實際上,雖然設定了 5px 的邊框寬度,但是實際上,上圖的邊框寬度只有 2.5px 的,這是由于另外一點一半邊框實際上被切割掉了,
因此,我們如果需要實作 5px 的效果,實際上需要 ctx.lineWidth =10,
當然,我們可以通過一個 CSS 變數來控制邊框的大小:
div {
position: relative;
width: 200px;
height: 64px;
margin: auto;
clip-path: polygon(var(--clipPath));
--clipPath: 85% 0%, 100% 50%, 85% 100%, 0% 100%, 0% 0%;
--borderWidth: 5;
&::before {
content:"";
position:absolute;
inset: 0;
mask: paint(borderDraw);
background: #000;
}
}
在實際的 borderDraw 函式中,我們將傳入的 --borderWidth 引數,乘以 2 使用就好:
registerPaint(
"borderDraw",
class {
static get inputProperties() {
return ["--clipPath", "--borderWidth"];
}
paint(ctx, size, properties) {
const borderWidth = properties.get("--borderWidth");
// ...
ctx.lineWidth = borderWidth * 2;
ctx.strokeStyle = "#000";
ctx.stroke();
}
}
);
這樣,我們每次都能得到我們想要的邊框長度:
CodePen Demo -- CSS Hudini & Unregular Custom Border
到這里,整個實作就完成了,整個程序其實有多處非常關鍵的點,會有一點點難以理解,具體可能需要自己實際除錯一遍找到實作的原理,
具體應用
在掌握了上述的方法后,我們就可以利用這個方式,實作各類不規則圖形的邊框效果,我們只需要傳入對于的 clip-path 引數以及我們想要的邊框長度即可,
好,這樣,我們就能實作各類不同的不規則圖形的邊框效果了,
像是這樣:
div {
position: relative;
width: 200px;
height: 200px;
clip-path: polygon(var(--clipPath));
--clipPath: 0% 15%, 15% 15%, 15% 0%, 85% 0%, 85% 15%, 100% 15%, 100% 85%, 85% 85%, 85% 100%, 15% 100%, 15% 85%, 0% 85%;
--borderWidrh: 1;
--color: #000;
&::before {
content:"";
position:absolute;
inset: 0;
mask: paint(borderDraw);
background: var(--color);
}
}
div:nth-child(2) {
--clipPath: 50% 0%, 61% 35%, 98% 35%, 68% 57%, 79% 91%, 50% 70%, 21% 91%, 32% 57%, 2% 35%, 39% 35%;
--borderWidrh: 2;
--color: #ffcc00;
}
div:nth-child(3) {
--clipPath: 90% 58%90% 58%, 69% 51%, 69% 51%, 50% 21%, 50% 21%, 39% 39%, 39% 39%, 15% 26%, 15% 26%, 15% 55%, 15% 55%, 31% 87%, 31% 87%, 14% 84%, 14% 84%, 44% 96%, 44% 96%, 59% 96%, 59% 96%, 75% 90%, 75% 90%, 71% 83%, 71% 83%, 69% 73%, 69% 73%, 88% 73%, 88% 73%, 89% 87%, 89% 87%, 94% 73%, 94% 73%;
--borderWidrh: 1;
--color: deeppink;
}
div:nth-child(4) {
--clipPath: 0% 0%, 100% 0%, 100% 75%, 75% 75%, 75% 100%, 50% 75%, 0% 75%;
--borderWidrh: 1;
--color: yellowgreen;
}
div:nth-child(5) {
--clipPath: 20% 0%, 0% 20%, 30% 50%, 0% 80%, 20% 100%, 50% 70%, 80% 100%, 100% 80%, 70% 50%, 100% 20%, 80% 0%, 50% 30%;
--borderWidrh: 3;
--color: #c7b311;
}
得到不同圖形的邊框效果:
CodePen Demo -- CSS Hudini & Unregular Custom Border
又或者是基于它們,去實作各類按鈕效果,這種效果在以往使用 CSS 是非常非常難實作的:
它們的核心原理都是一樣的,甚至加上 Hover 效果,也是非常的輕松:
完整的代碼,你可以戳這里:CodePen Demo -- https://codepen.io/Chokcoco/pen/ExRLqdO
至此,我們再一次利用 CSS Painting API 實作了我們過往 CSS 完全無法實作的效果,這個也就是 CSS Houdini 的魅力,是 JS In CSS 的魅力,
兼容性?
好吧,其實上一篇文章也談到了兼容問題,因為可能有很多看到本篇文章并沒有去翻看前兩篇文章的同學,那么,CSS Painting API 的兼容性到底如何呢?
CanIUse - registerPaint 資料如下(截止至 2022-11-23):
Chrome 和 Edge 基于 Chromium 內核的瀏覽器很早就已經支持,而主流瀏覽器中,Firefox 和 Safari 目前還不支持,
CSS Houdini 雖然強大,目前看來要想大規模上生產環境,仍需一段時間的等待,讓我們給時間一點時間!
最后
好了,本文到此結束,希望本文對你有所幫助 ??
如果還有什么疑問或者建議,可以多多交流,原創文章,文筆有限,才疏學淺,文中若有不正之處,萬望告知,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/540254.html
標籤:Html/Css
