使用CSS Houdini制作平滑的圆角
最近,我在Twitter分享了一篇关于用户界面的文章。我喜欢Illusions,但这对我来说是一个新的东西:一个修正的圆比几何图形看起来更圆!圆角矩形也是这样。令人惊讶的是,我还发现,自iOS7以来,苹果一直在使用这个技巧。在数学上,它被称为**Lamé Curve or Superellipse**。
iOS 6和iOS 7图标的不同(来源)。
与此同时,我正在用Houdini的渲染API(Paint API)做一个演讲。这个API定义了一种绘制内容的新方法,渲染引擎将内容绘制到一个<image>
上。它主要提供了编程方式绘制图像作为背景的能力。因此,绘制一个超椭圆(Superellipse)就很非常的简单。
从iOS 7开始,App的图标已经开始使用一种叫“超椭圆”(Superellipse)的形状。因为苹果公司并未发布一个官方的形状模板,所以你要用这个非官方模板 来准确地确定图标将会被折叠多少。
几周后,Sketch发布了一个新版本,并引入了**“平滑圆角”(Smooth Corners)**功能,据我所知,这是一个超随圆(Superellipse)。我喜欢这个名字,所以我们在CSS也应该创建平滑的圆角。
首先需要添加一个新的paintWorklet
模块(paintWorklet
只在CSS(Canary)或Window(stable)上有)。
(CSS.paintWorklet || paintWorklet).addModule('smooth-corners.js')
然后加截文件,加截的这个文件中注册了一个叫做smooth-corners
,这个用了一种绘制超椭圆(QT Codebase的算法)的绘图方法:
registerPaint('smooth-corners', class {
paint(ctx, size) {
ctx.fillStyle = 'black'
// n=4 draw a squircle
const n = 4
let m = n
if (n > 100) m = 100
if (n < 0.00000000001) m = 0.00000000001
const r = size.width / 2
const w = size.width / 2
const h = size.height / 2
ctx.beginPath();
for (let i = 0; i < (2*r+1); i++) {
const x = (i-r) + w
const y = (Math.pow(Math.abs(Math.pow(r,m)-Math.pow(Math.abs(i-r),m)),1/m)) + h
if (i == 0)
ctx.moveTo(x, y)
else
ctx.lineTo(x, y)
}
for (let i = (2*r); i < (4*r+1); i++) {
const x = (3*r-i) + w
const y = (-Math.pow(Math.abs(Math.pow(r,m)-Math.pow(Math.abs(3*r-i),m)),1/m)) + h
ctx.lineTo(x, y)
}
ctx.closePath()
ctx.fill()
}
})
关于paint
方法的参数:
ctx
是一个PaintRenderingContext2D
对象,也是CanvasRenderingContext2D
的子集,所以你可以(大部分)画任何你想要的图形size
是一个PaintSize
对象,它是绘制图形的大小
现在,在CSS中就可以使用paint()
函数,绘制一个带有平滑圆角的黑色矩形:
.el {
background: paint(smooth-corners);
}
为了简单起见,我们将使用生成的图像作为CSS Mask的图像源。这样,我们就可以很容易的使用背景颜色、渐变和背景图像设置背景。
.el {
background: linear-gradient(deeppink, orangered);
mask-image: paint(smooth-corners);
}
这很好,但不可靠。现在,我们要画一个特殊的超椭圆,命名为squircle3
。因为n
变量被设置为4
。那么,如何用不同的指数绘制超椭圆呢?iOS图标使用的指数是5
。让我们用CSS自定义属性来做。
首先,使用一个--smooth-corners
自定义属性:
.el {
--smooth-corners: 4;
background: linear-gradient(deeppink, orangered);
mask-image: paint(smooth-corners);
}
然后在registerPaint
函数中获得该值:
registerPaint('smooth-corners', class {
static get inputProperties() {
return [
'--smooth-corners'
]
}
paint(ctx, size, styleMap) {
const exp = styleMap.get('--smooth-corners').toString()
const n = exp
}
})
注意,paint()
方法接收第三个参数styleMap
,它是一个相对计算值的API,主要用于inputProperties
中列出的属性。这里我们得到--smooth-corners
值和使用n
变量来表示它。
现在的全部功能是,我们可以在CSS中编辑--smooth-corners
。如果使用了CSS.registerProperty
,该属性也可以被激活。
目前,只有Chrome4支持Houdini的Paint API,所以我们需要做渐进式增强:
.el {
border-radius: 60px;
background: linear-gradient(...)
}
@supports (mask-image: paint(smooth-corners)) {
.el.is-loaded {
border-radius: 0;
mask-image: paint(smooth-corners);
--smooth-corners: 5;
}
}
另外,Houdini类似JS-in-CSS,最好是等JavaScript加载或准备就续再使用。在这里,我决定给元素添加一个.is-loaded
类名。
在生产过程中,我们可以使用一个PostCSS插件来自动化编写我们的CSS。
这里有一个CSS Houdini的示例仓库,感兴趣的可以看看。
注释
使用CSS Mask基本上就是在盒子外套了一层面具。如果真的需要的话,你应该从registerPaint
中绘制一个渐变或一个图像(但是<image>
类型还不太支持,所以你现在必须想办法处理它)。
如果你想做一些实验,那你可以看看其他人是怎么绘制图像:创建你自己的背景属性,例如background-opacity
或者如何通过参数而不是属性绘制一个渐变的圆角。我很期待你的分享。
本文根据@Vincent De Oliveira 的《Smooth corners with CSS Houdini》所译,整个译文带有我们自己的理解与思想,如果译得不好或有不对之处还请同行朋友指点。如需转载此译文,需注明英文出处:http://iamvdo.me/en/blog/smooth-corners-with-css-houdini。
如需转载,烦请注明出处:https://www.fedev.cn/css/smooth-corners-with-css-houdini.html