在 Blink 中实现 CSS 边角形状的极端情况

发布时间:2026 年 2 月 19 日

Chrome 在 2025 年发布的一项 CSS 功能是 corner-shape。这样,您就可以使用 bevelscoop 等关键字定义具有 border-radius 的角部的形状。您还可以使用接收介于 -InfinityInfinity 之间的值的 superellipse 函数。

如需大致了解此功能及其运作方式,请参阅 Amit Sheen 在 Frontend Masters 上发表的详尽文章

在 2025 年初实现此功能时,我遇到了一些复杂程度各异的有趣挑战。我学到了很多关于超椭圆、Blink 中的边框绘制以及使用矢量数学进行 2D 图形处理的知识。

本文档分享了我学到的一些知识,这些知识可能对其他人也有用。

凸形和凹形的对称性

虽然 superellipse (k) 值通常介于 0 和 Infinity 之间,其中 0 到 1 之间的值是凹的,其余值是凸的(1 为 bevel),但 CSS 规范中的 superellipse 值介于 -InfinityInfinity 之间,表示 2k。这样就形成了对称性,因为任何正值看起来都像是其对应负值的镜像。

不过,默认情况下,superellipse 公式不会按这种方式运行。

superellipse 的公式为:xk + yk = 1。反向公式 x1/k + y1/k = 1 不会生成视觉上对称的曲线。

例如,如果 k2

超椭圆曲线的比较,显示了圆形超椭圆(蓝色)、具有规范公式的凹形超椭圆(红色)和视觉上对称的曲线(黄色)。
  • 蓝色曲线表示舍入后的 superellipse (y=xn)。
  • 红色曲线表示具有规范公式 (y=x1/n) 的 scoop superellipse
  • 黄色曲线表示与蓝色曲线 (y=1-(1-x)n) 在视觉上对称的曲线。

如图所示,形状并不相同!

我不会深入探讨其中的数学原理,但它与对偶范数以及我们感知曲率的方式有关。

就规范和实现而言,我们在此处表示的是某种视觉效果,因此在计算凹形时,我们使用对称等效项。其余数学运算则针对凸形(k>=1 或正超椭圆值)进行。

闭合形式的公式

下一个挑战是以闭合形式(即由简单算术运算组成的公式)表示曲线或 superellipse 的周长。 这对于性能至关重要,可让系统将 superellipse 渲染交给图形引擎。

Skia 等图形引擎熟悉贝塞尔曲线,因此使用少量贝塞尔曲线来表示 superellipse 并近似表示其周界,可提高 superellipse 曲线的渲染性能。

幸运的是,通过使用符号回归,我们可以找到一个公式,将半个凸角表示为一条三次贝塞尔曲线。

三次贝塞尔曲线有四个点:

  • 第一个点是 (0, 1)。
  • 最后一个点是实际的超椭圆半角:0.51/k,0.51/k
  • 第一个控制点在与起点相同的水平位置拉伸:(a, 1)。
  • 第二个控制点位于半角对角线位置:(0.51/k - b,0.51/k + b)

这里使用的半角值恰好是一个非常重要的坐标,我们将在后续的其他计算中使用它。

其中,ab 是使用符号回归从 k 计算得出的。

将控制点映射到曲线的图示。
如需查看演示,请参阅此 Codepen。

计算这四个点并在它们之间渲染三次贝塞尔曲线,即可获得具有给定 k 的闭合形式凸半角。然后,我们可以旋转结果以填充其余的边角,将其应用于其他边角,并翻转它们以渲染凹面等效项。

我们不再深入探讨数学细节,计算 ab 的公式如下:

p0 = 1.2430920942724248
p1 = 2.010479023614843
p2 = 0.32922901179443753
p3 = 0.2823023142212073
p4 = 1.3473704261055421
p5 = 2.9149468637949814
p6 = 0.9106507102917086

s = log2(k)
slope = p0 + (p6 - p0) * 0.5 * (1 + tanh(p5 * (s - p1)))
base = 1 / (1 + exp(slope * p1))
logistic = 1 / (1 + exp(slope * (p1 - s)))

a = (logistic - base) / (1 - base)
b =  p2 * exp(-p[3] * (s ^ p4))

边框和阴影

除了计算角部周边的路径外,系统还会计算向内偏移(边框或内边距 box-shadow)或向外偏移(outline 或普通 box-shadow)时的外观。在传统的图形库中,这是通过描边来实现的。

不过,CSS 中的边框和阴影具有与描边不同的渲染特征:

  • 边框不均匀。
  • 例如,上边框可以是 10 像素,右边框可以是 5 像素,而边角则在这两者之间进行插值。
  • 此外,它们是向内弯曲,而不是向两侧弯曲。
  • 阴影和轮廓的渲染效果与描边不完全相同。
  • 而是会进行调整,使边角看起来清晰。

虽然常规的边框和阴影渲染路径适用于圆形或比圆形更凸的 corner-shape 值(例如 squircle),并且可以旋转 90 度以用于比 scoop 更凹的形状,但此默认值不适用于介于 -1 和 1 之间的 corner-shape 值,因为将边框或阴影平行于边缘进行偏移会产生宽度不均匀的边角。

例如,取一个 bevel 角,并将边框向两侧偏移若干像素,即可产生“腹部”效果,即角的中部看起来比两侧宽。

为了解决这个问题,我们的目标是创建一种类似于描边的效果,即找到起始处角曲线的法线,并使其长度与 bordershadow-spread 的宽度相同。

幸运的是,这仅适用于子椭圆(介于 bevel 和圆形之间),而 squircle 等超椭圆可按预期运行。

若要找到子椭圆曲线的法线,只需找到其二次曲线对应物的法线即可,因为子椭圆及其二次曲线等效物彼此接近。

使用之前计算出的相同半角,您可以找到具有相同中点的二次曲线,推导出其二次控制点,然后直接计算出法线。

法线继续以与 border-widthshadow-spread 相同的长度延伸,然后使用边缘(边框为内边缘,阴影为外边缘)剪裁生成的曲线,以创建连续的路径。

带边框的角落的图示,显示了如何扩展法线以定义边框形状。
在 CodePen 上查看此示例。

虽然还有更精确的数学方法来计算 superellipse 的正切,但此方法效率高,并且能生成足够的结果来渲染边框和阴影。

颜色联接

浏览器中发生的一种有趣的绘制行为在 CSS 中未指定。它会呈现具有不均匀颜色或样式的边框。 例如,元素具有绿色实线上边框和黄色虚线右边框。在这些情况下,斜接是位于边框边缘的相关角和内边距边缘的相关角之间的切线。它会在相邻边缘之间创建边界。虽然未指定,但渲染在浏览器之间具有一定的统一性。

在 Blink(以及其他浏览器)中,此功能的实现方式如下。即将绘制的边会粗略地剪裁,就像在斜接处相交的多边形一样,以包含相关边但不包含任何其他边的方式进行计算。这样可以避免溢出,即以错误的样式和颜色绘制其他边缘。

到目前为止,此多边形的计算相对简单,因为对于规则的圆角,角区域永远不会重叠。不过,对于凹超椭圆(负 superellipse 值),情况会有所不同。这些形状可能会形成非常有趣的形状,导致简单的相交多边形非常容易出现重叠和“溢出”现象。

请考虑以下 CSS:

.weird {
  width: 200px;
  height: 200px;
  corner-shape: scoop round;
  border-radius: 80% 20% / 50% 50%;
  border-width: 10px;
  border-color: orange purple black blue;
  border-style: solid dotted;
}
具有不均匀边框的 CSS 元素示例,显示了橙色、紫色虚线、黑色和蓝色虚线边缘。

我们希望分别剪裁每个边缘(橙色、紫色虚线、黑色、蓝色虚线),然后绘制路径。

为了在不与另外三个角重叠的情况下实现此效果,需要进行仔细的剪裁。

例如,考虑橙色(顶部)边缘。

很难找到一个包含整个边缘且不会延伸到紫色、黄色甚至黑色边缘的多边形。其他一些形状则更具挑战性。

此过程涉及三个剪辑。

第一个剪辑包含整个边,以及完整的角(无斜接)。例如:

一种剪切角形状,表示两个角(一个凹角,一个圆角)。

此形状由两个角(一个 scoop 角,一个圆角)组成,两者之间有最小的边,并在末端相连。

从这种形状开始,可以消除与对边的重叠,现在只需考虑两个斜接。

为此,我们从该角剪切出一个位于边框边缘角和内边距边缘角之间、在即将与边缘相交时停止的多边形:

要剪裁的区域的演示。

系统会找到从边框边缘到内边距边缘的直线与曲线从相关起点开始的切线相交的点(如果曲线是凹的)。

如果该点位于渲染区域内,则该进程会在此处停止,并沿该切线继续,直到再次遇到边框,从而完成一个四边形。

否则,简单的三角形可能会被剪裁。

摘要

Web 平台为 Web 设计者和开发者提供了强大的表达能力。 有时,采用单个数值的 CSS 属性会在底层隐藏大量复杂性,以确保准确且一致地呈现。

corner-shape 功能的复杂程度令人惊讶。本文档旨在帮助未来在 Blink、其他浏览器或规范中开发此功能的开发者。