使用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

大漠

常用昵称“大漠”,W3CPlus创始人,目前就职于手淘。对HTML5、CSS3和Sass等前端脚本语言有非常深入的认识和丰富的实践经验,尤其专注对CSS3的研究,是国内最早研究和使用CSS3技术的一批人。CSS3、Sass和Drupal中国布道者。2014年出版《图解CSS3:核心技术与案例实战》。

如需转载,烦请注明出处:https://www.fedev.cn/css/smooth-corners-with-css-houdini.html