我敢说,大多数时候我们在 CSS 中对盒子角进行圆角处理时,都是对整个边框应用统一的 border-radius 值。这在许多设计中都是一种不错的修饰。但有时我们可能希望不同角落有不同的圆角半径。很简单吧?这样一来,该属性就需要四个值。但事实证明,我们可能会陷入一个角落,因为圆角边框可能相互重叠。
我们许多人知道获取“药丸形”矩形的常见“999em hack”
我们将border-radius
设置为一个荒谬的大数字,比如 999em 或 999vmax,而不是变成某种不可能的、埃舍尔式的莫比乌斯带,而是将角落 nicely rounded off to form a semicircle. 这很方便,因为这意味着我们不需要知道矩形的尺寸即可实现这种效果——它“just works”。
这个技巧的起源对我来说很模糊,但我在一篇Lea Verou 博客的评论中看到了一个早期示例,作者是David Baron。
但是,就像许多“hack”一样,我们在某些边缘情况下可能会遇到一些奇怪的行为。例如,为什么这个有效,而这个无效
我们希望矩形的右侧为“药丸形”,而左侧的角圆角为 40px。但我们的 40px 角不见了!它们去哪儿了?

答案是,它们没有去任何地方;浏览器只是将它们的值减少到接近零,因此它们看起来像消失了。
浏览器以某种方式偏离了我们请求的值,但它何时以及如何决定介入?让我们检查一下规范
令 f = min(Li/Si),其中 i ∈ {top, right, bottom, left},Si 是边 i 上两个相应角的半径之和,Ltop = Lbottom = 盒子的宽度,Lleft = Lright = 盒子的高度。如果 f < 1,则所有角半径都会减少,方法是将它们乘以 f。
啊,这解释了这一切!祝您本周剩余时间愉快!:wipes hands conclusively
…当然,我在开玩笑。这需要一些解码,所以让我们从数学和几何角度来看待它。始终牢记,此公式的目的是防止半径重叠。事实上,这就是“999em hack”起作用的原因!
我的意思如下。
简单来说,浏览器基本上是这么想的:“按比例缩小所有半径,直到它们之间不再重叠。”(注意,是半径不能重叠;它们形成的圆圈确实可能重叠。)
但是计算机不理解英语,所以公式的作用是:
首先,它计算矩形每条边的长度与其相邻半径之和的比率。因此,在我们标准的“药丸 hack”中,它会计算出
[Width of Side] / [Adjacent Border Radius 1 + Adjacent Border Radius 2]
Top: 200px / (400px + 400px) = 0.25
Right: 100px / (400px + 400px) = 0.125
Bottom: 200px / (400px + 400px) = 0.25
Left: 100px / (400px + 400px) = 0.125

然后,它将所有半径乘以这些比率中的最小值。最小比率为 0.125,所以我们将用它乘以我们最初的 400px 半径
400px * 0.125 = 50px
这使得所有半径都变为 50px。对于最短边为 100px 的矩形,这将为我们提供完美的药丸形状。太酷了!请查看以下动画
(为了方便查看,我们在这里使用 400px 来表示我们的“荒谬的大”半径,而不是 999em;只要它们至少是矩形最短边的长度的一半,它们就会重叠。)
表示我们指定的半径的圆圈从它们请求的大小开始,然后按上述公式所规定的比率缩小。需要注意的是,它们全部按相同比率缩小。这也许更直观,因为它们一开始就都是相同的大小。
现在让我们回到我们的“损坏”示例,它让我们开始了这一切。
这里发生了什么?让我们尝试一个不那么极端的例子,以显示受此缩小影响的边框半径,但不会像它们几乎消失那样严重
我们可以看到,我们没有得到我们在左上角和左下角请求的 40px 半径,但我们得到了一些东西。让我们再次使用该公式,首先找到所有边与其相邻半径之间的比率
Top: 200px / (40px + 400px) = 0.455
Right: 100px / (400px + 400px) = 0.125
Bottom: 200px / (40px + 400px) = 0.455
Left: 100px / (40px + 40px) = 1.25
同样,我们这里最小的比率是 0.125,所以我们将所有指定的半径乘以这个值,得到右角的 50px 半径和左角的 5px 半径。
该公式在这里确保的是,矩形右侧的两个大半径不会重叠,但这样做会导致左边的半径缩小得比它们“需要缩小”以防止顶部、底部和左侧半径重叠还要多。
以下是一个更丰富的示例,展示了不同情况下会发生什么。尝试更改一些值,看看会发生什么。同样,大尺寸是我们代码中指定的半径,而小尺寸是浏览器协调这些半径以防止重叠的方式。
为什么那些像奥兹一样的神秘的规范制定者会决定以这种方式进行?为什么不先缩小较大的边框半径,而不是从一开始就缩小所有半径?
我当然不能读懂他们的心思,但这种方法的好处是,半径保持彼此之间的比例。如果我们指示浏览器减少最大的半径,直到不再重叠或直到它等于第二大的半径(以先发生者为准)并重复,那么我们的“混合药丸 hack”将起作用;但是,在某些情况下,最终可能会得到四个相等的半径,而用户却要求使用非常不同的尺寸。换句话说,实现必须以某种方式对数字“不忠实”,而这是他们选择的方式(我认为很明智)。
感谢我的同事 Catherine 首先注意到这个“消失的半径”问题,以及James 帮助我理解规范!
我真的很喜欢公式。我知道从 Lea 的演讲中可以了解到收缩行为 https://youtu.be/b9HGzJIcfDE 但是不知道有公式。这个公式是规范的一部分,还是浏览器厂商自行决定的?
我制作了这个边框圆角工具,您可以在其中单独移动所有八个手柄。这或许有助于了解当两边的总和超过 100% 时会发生什么。过去,即使我制作了这个工具,我仍然不明白它。因此,非常感谢这篇文章。
https://9elements.github.io/fancy-border-radius/full-control.html#41.38.37.34-68.49.51.64-.
我真的很喜欢你的工具。谢谢!
我从未听说过 999em hack。设置可以理解的值似乎更容易。例如,要创建一个“药丸”按钮:
border-radius: calc(var(--button-height) / 2) / 50%;
我同意。从未听说过它,如果我偶然发现它,它可能会让我感到困惑……
css 提供变量和函数,在我看来,它们更易读,而且不像 hack 那样多。
感谢!如果您对元素的大小是可预测的,您的解决方案效果很好;如果元素的大小不可预测(例如,如果元素内部将包含未知数量的文本),则无法为边框圆角指定固定值。同样,如果您正在使用一个设计系统,其中例如填充可能会改变,或者字体大小可能会改变,使用边框圆角的固定值可能会导致一些不可预测的行为。999em hack 确保您的元素始终具有药丸式角。
我总是使用
border-radius: 100vh
。这样看起来就像 100%,便于记忆。非常棒的文章,但我建议将这一部分
“(为了方便查看,我们在这里使用 400px 来表示我们的“荒谬的大”半径,而不是 999em;只要它们至少是矩形最短边的长度的一半,它们就会重叠。)”
放到解释中的较高位置,因为我花了很多时间才弄清楚你在第一个示例中是如何得到 400px 的 :P