本文是 CSS Houdini 之 CSS Painting API 系列第四篇。
在上三篇中,我们详细介绍了 CSS Painting API 是如何一步一步,实现自定义图案甚至实现动画效果的!
在这一篇中,我们将继续探索,尝试使用 CSS Painting API,去实现过往 CSS 中非常难以实现的一个点,那就是如何绘制不规则图形的边框。
再简单快速的过一下,什么是 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 的一个难点之一。在过往,虽然我们有很多方式利用 Hack 出不规则图形的边框,我在之前的多篇文章中有反复提及过:
我们来看看这样一个图形:
利用 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 就不那么好实现了,像是这样:
在过往,有两种相对还不错的方式,去实现这样一个不规则图形的边框:
drop-shadow()
我们快速回顾一下这两个方法。
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 滤镜,再通过 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 的黑色图块这样,也可以实现不规则图形的边框效果:
CodePen Demo -- 3 ways to achieve unregular border[8]
那么,到了今天,利用 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();
}
}
);
这样,我们就得到了图形的边框效果:
但是,仅仅利用 [bacg](background: paint(borderDraw "bacg"))
来绘制边框效果,会有一些问题。
上述的图形,我们仅仅赋予了 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))
剪切掉外围的图形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[9]
到这里,整个实现就完成了,整个过程其实有多处非常关键的点,会有一点点难以理解,具体可能需要自己实际调试一遍找到实现的原理。
在掌握了上述的方法后,我们就可以利用这个方式,实现各类不规则图形的边框效果,我们只需要传入对于的 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[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
本文由哈喽比特于1年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/mFIso1TrMcjr0XG4CaPXzw
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。