共计 10162 个字符,预计需要花费 26 分钟才能阅读完成。
本文是 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 class="arrow-button"></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 就不那么好实现了,像是这样:
<img width=”225″ alt=”image” src=”https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/833b647d5c3644b49a31486237ce2d23~tplv-k3u1fbpfcp-zoom-1.image”>
在过往,有两种绝对还不错的形式,去实现这样一个不规则图形的边框:
- 借助 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()
是用于生成暗影的,然而多层值很小的暗影叠加下,居然有了相似于边框的成果:
<img width=”222″ alt=”image” src=”https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/381f6745fb3c4e709ee5203a204425ac~tplv-k3u1fbpfcp-zoom-1.image”>
借助 SVG 滤镜实现实现不规则边框
另外一种形式,须要把握比拟深的 SVG 滤镜常识。通过实现一种非凡的 SVG 滤镜,再通过 CSS 的 filter 引入,实现不规则边框。
看看代码:
<div></div>
<svg width="0" 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″ 来管制边框的大小
这样,也能够实现不规则图形的边框成果:
<img width=”222″ alt=”image” src=”https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/702be1594baa47af8843dda6483b22f1~tplv-k3u1fbpfcp-zoom-1.image”>
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%;);
}
咱们能够失去这样一个图形:
<img width=”219″ alt=”image” src=”https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d52e4628f7db4638902614cb11b9f09d~tplv-k3u1fbpfcp-zoom-1.image”>
当然,本文的配角是 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()
给图形上色
这样,咱们就失去了这样一个图形:
<img width=”212″ alt=”image” src=”https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/324a7d18b6cb400f87550cb074d2b818~tplv-k3u1fbpfcp-zoom-1.image”>
都拿到了残缺的图形了,那么咱们只给这个图形绘制边框,不上色,不就失去了它的边框成果了吗?
简略革新一些 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();}
}
);
这样,咱们就失去了图形的边框成果:
<img width=”218″ alt=”image” src=”https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b8a6163022fe4a7aafebca82906e313f~tplv-k3u1fbpfcp-zoom-1.image”>
仅仅利用 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();}
}
);
此时,整个图形会变成:
<img width=”237″ alt=”image” src=”https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e19315a39cc54befba1cb27ca3b55c64~tplv-k3u1fbpfcp-zoom-1.image”>
能够看到,没有展现残缺的 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
,再看看成果:
<img width=”217″ alt=”image” src=”https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/2a111335a5dd4fc2925ba92fad430aff~tplv-k3u1fbpfcp-zoom-1.image”>
看上去不错,然而实际上,尽管设置了 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();}
}
);
这样,咱们每次都能失去咱们想要的边框长度:
<img width=”209″ alt=”image” src=”https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/7b8cbd9f9df2421c8d62d5dbb4901244~tplv-k3u1fbpfcp-zoom-1.image”>
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;
}
失去不同图形的边框成果:
<img width=”1690″ alt=”image” src=”https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d577e5b5b01946cbb49186064d637130~tplv-k3u1fbpfcp-zoom-1.image”>
CodePen Demo — CSS Hudini & Unregular Custom Border
又或者是基于它们,去实现各类按钮成果,这种成果在以往应用 CSS 是十分十分难实现的:
<img width=”1131″ alt=”image” src=”https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/8b017bc79b4d407c92daf7c8c4f12f94~tplv-k3u1fbpfcp-zoom-1.image”>
它们的外围原理都是一样的,甚至加上 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 尽管弱小,目前看来要想大规模上生产环境,仍需一段时间的期待。让咱们给工夫一点工夫!
最初
好了,本文到此结束,心愿本文对你有所帮忙 :)
如果还有什么疑难或者倡议,能够多多交换,原创文章,文笔无限,满腹经纶,文中若有不正之处,万望告知。