前端开发者学堂 - fedev.cn

图解CSS:border-radius

发布于 大漠

CSS的border-radius属性是属于 CSS Backgrounds and Borders Module Level 3 的一部分。随着CSS技术的不断变革,border-radius除了我们熟悉的物理属性之外新添加了逻辑属性。而且border-radius取值不同,绘制的圆角效果也会有所差异。当然,在很多开发者眼中,border-radius已经是非常简单的一个特性,但试问一下,你真的理解了该特性呢:

  • border-radius取不同值会发生什么?
  • border-radius取值为%值时相对于谁计算?
  • border-radius嵌套会发生什么?
  • border-radius的半径重叠时会发生什么?

如果你不能非常明确的回答上面提到这几个问题,那么接下来的内容就值得你花时间阅读。

border-radius基本特性

CSS中的border-radius主要作用是用来给元素绘制圆角效果。它和border属性类似,是一个简写属性,如果不使用简写属性的话,border-radius属性可以拆分为出border-top-left-radiusborder-top-right-radiusborder-bottom-left-radiusborder-bottom-right-radius四个属性。

不过随着 CSS逻辑属性 的出现,border-radius的逻辑圆角属性由border-start-start-radiusborder-start-end-radiusborder-end-start-radiusborder-end-end-radius组成。不过,CSS逻辑圆角属性会受writing-modedirectiontext-orientation属性的影响。

CSS的物理圆角和逻辑圆角有相应的映射关系:

来看一个简单地示例:

尝试着调整上面示例中不同值的设置,在不同的场景中看到的效果会不一样:

特别声明,为了不给大家带来概念上的混淆,接下来的内容都将围绕着物理圆角来展开

CSS的border-radius取值和我们熟悉的其他CSS属性(比如marginpadding)还是有所差异的。因为border-radius可以取分x轴和y轴取值,而且每个轴取值个数可以是{1, 4}(一到四个)。

border-radius: 	<length-percentage>{1,4} [ / <length-percentage>{1,4} ]?

而且border-radiusxy轴之间必须得由/来分隔。如果在border-radius中未显式使用/分隔符,则表示x轴和y轴的值相等。

注意,使用/符来分隔x轴和y轴的半径值时,/前后最好有一个空格。

单个值

让我们从最基础开始。希望这不会让你感到厌烦。毕竟CSS的border-radius已经存在好几年了。

当你给border-radius只显式设置一个值时,元素所有的角都会用这个值作为圆角半径

.element {
    border-radius: 120px;
}

正如上图所示,border-radius取一个值时,除了四个圆角半径相同之外,而且x轴和y轴的都相同,即:

.element {
    border-radius: 120px;
}

// 等同于

.element {
    border-radius: 120px / 120px;
}

两个值

border-radius取两个值时有两种场景:

.element {
    border-radius: 60px 120px;
}

第一个值表示左上角和右下角圆角半径,第二个值表示右上角和左下角圆角半径。相当于:

.element {
    border-top-left-radius: 60px;
    border-bottom-right-radius: 60px;

    border-top-right-border: 120px;
    border-bottom-left-radius: 120px;
}

另外一个场景就是x轴和y轴的值不同:

.element {
    border-radius: 60px / 120px;
}

上面这个代码同样表示的是元素所有圆角效果相同,只不过每个圆角的x轴半径为60pxy轴的半径为120px

三个值

border-radius取三个值的组合场景相对来说更为复杂一些,可以是:

border-radius: 40px 60px 80px;
border-radius: 40px 60px / 80px;
border-radius: 40px / 60px 80px;

这三种使用方式最终渲染效果都将会不一样:

你可能从上图中已经发现了他们之间的差异,这里我们着重来看border-radius: 40px 60px 80px

四个值

你可能可以感觉得到,随着取值个数越多,组合场景就会越多,比如四个值:

border-radius: 40px 60px 80px 100px;
border-radius: 40px / 60px 80px 100px;
border-radius: 40px 60px / 80px 100px;
border-radius: 40px 60px 80px / 100px

上面不同的值带来的效果也是不一样的:

八个值

我们跳过其他几个取值的演示,直接来看border-radius取八个值效果。

.element {
    border-radius: 30px  40px 50px 60px / 60px 50px 40px 30px;
}

就上面的示例而言,会令元素的每个圆角的x轴和y轴的半径不同,/分隔符之前的值表示:

  • 第一个值代表的是左上角x轴的圆角半径
  • 第二个值代表的是右上角x轴的圆角半径
  • 第三个值代表的是右下角x轴的圆角半径
  • 第四个值代表的是左下角x轴的圆角半径

/分隔符之后的值表示:

  • 第一个值代表的是左上角y轴的圆角半径
  • 第二个值代表的是右上角y轴的圆角半径
  • 第三个值代表的是右下角y轴的圆角半径
  • 第四个值代表的是左下角y轴的圆角半径

即:

border-radius: 30px 40px 50px 60px / 60px 50px 40px 30px;

/* 等同于 */
border-top-left-radius: 30px 60px;
border-top-right-radius: 40px 50px;
border-bottom-right-radius: 50px 40px;
border-bottom-left-radius: 60px 30px;

具体的效果如下:

简单地小结一下。

如果border-radius取值中未显式地使用/分隔符的话,则表示border-radiusx轴和y的圆角半径相同。不管是x轴还是y轴取值都可以是 1~4 值,就此方面和我们熟悉的marginpadding取值类似:

  • 如果只有一个值,则表示所有圆角的半径都相同
  • 如果有两个值,则第一个值是左上角、右下角的圆角半径,第二个值是右上角和左下角的圆角半径
  • 如果有三个值,则第一个值是左上角的圆角半径,第二个值是右上角和左下角的圆角半径,第三个值是右下角的圆角半径
  • 如果有四个值,则第一个值是左上角的圆角半径,第二个值是右上角的圆角半径,第三个值是右下角圆角半径,第四个值是左下角圆角半径

上面这个取值规则适用于xy的取值方式。

CSS border-radius除了能绘制不同的圆角效果之外(比如上面示例所示),而且还可以绘制一些图形(CSS实现一些视觉效果):

border-radius中的单位

在上面示例中我们演示的Demo,给border-radius设置的单位都是px单位(一个固定单位)。事实上border-radius属性值的单位可以是<length-percentage>,即<length><percentage>

可以用于<length>的单位有:

  • 固定单位:pxpt,还有不太常用的cmmminpc
  • 相对单位:emremexchvwvhvmaxvmin等,其中emremexch是相对于font-size计算,vwvhvmaxvmin是相对于视窗计算

<percentage>就是我们熟悉的%单位。

如果你想深入了解CSS单位和值相关的知识的话,可以花点时间阅读 图解CSS系列 中《CSS 的值和单位》一文。

相比而言,%的取值是最为复杂的。特别是对于初学者而言,可能不太了解border-radius值为%时,它是相对于谁做计算。 如果你对这方面也不太清楚的话,那么可以接着往下阅读,如果你对这方面了解的话,可以跳过这节,继续下一节的内容。

border-radius使用%值时,它计算的相对值是需要分开来算的,其中 x轴的%值相对于元素的width值计算;y轴的%值相对于元素的height值计算,比如:

.element {
    width: 300px;
    height: 300px;
    border-radius: 30% 70% 20% 40%;
}

上面示例中border-radius: 30% 70% 20% 40%;对应的计算结果是:

左上角(top-left)⇒ border-top-left-radius: 30% ⇒ x = y = 300px x 30% = 90px
右上角(top-right) ⇒ border-top-right-radius: 70% ⇒ x = y = 300px x 70% = 210px
右下角(bottom-right)⇒ border-bottom-right-radius: 20% ⇒ x = y = 300px x 20% = 60px
左下角(bottom-left)⇒ border-bottom-left-radius: 40% ⇒ x = y = 300px x 40% = 120px

 效果看上去像下图这样:

上面示例是元素widthheight相等,如果元素widthheight不相等时:

.element {
    width: 600px;
    height: 300px;
    border-radius: 30% 70% 20% 40%;
}

这个时候,border-radius: 30% 70% 20% 40%;对应的计算结果是:

左上角(top-left)⇒ border-top-left-radius: 30% ⇒ x = 600px x 30% = 180px; y = 300px x 30% = 90px
右上角(top-right)⇒ border-top-right-radius: 70% ⇒ x = 600px x 70% = 420px; y = 300px x 70% = 210px
右下角(bottom-right)⇒ border-bottom-right: 20% ⇒ x = 600px x 20% = 120px; y = 300px x 20% = 60px
左下角(bottom-left)⇒ border-bottom-left: 40% ⇒ x = 600px x 40% = 240px; y = 300px x 40% = 120px

效果看上去像下图这样:

如果元素的widthheight相等,但border-radius属性的值是一个带/符号的八个值:

.element { 
    width: 300px; 
    height: 300px; 
    border-radius: 70% 30% 30% 70% / 60% 40% 60% 40%; 
}

对应的计算如下:

左上角(top-left)⇒ border-top-left-radius: 70% 60% ⇒ x = 300px x 70% = 210px; y = 300px x 60% = 180px
右上角(top-right)⇒ border-top-right-radius: 30% 40% ⇒ x = 300px x 30% = 90px; y = 300px x 40% = 120px
右下角(bottom-right)⇒ border-bottom-right-radius: 30% 60% ⇒ x = 300px x 30% = 90px; y = 300px x 60% = 180px
左下角(bottom-left) ⇒ border-bottom-left-radius: 70% 40% ⇒ x = 300px x 70% = 210px; y = 300px x 40% = 120px

对应的效果如下:

同样的,如果元素的widthheight值不同时,计算方式相似:

.element {
    width: 600px;
    height: 300px;
    border-radius: 70% 30% 30% 70% / 60% 40% 60% 40%;
}

对应的计算如下:

左上角(top-left)⇒ border-top-left-radius: 70% 60% ⇒ x = 600px x 70% = 420px; y = 300px x 60% = 180px
右上角(top-right)⇒ border-top-right-radius: 30% 40% ⇒ x = 600px x 30% = 180px; y = 300px x 40% = 120px
右下角(bottom-right)⇒ border-bottom-right-radius: 30% 60% ⇒ x = 600px x 30% = 180px; y = 300px x 60% = 180px
左下角(bottom-left) ⇒ border-bottom-left-radius: 70% 40% ⇒ x = 600px x 70% = 420px; y = 300px x 40% = 120px

对应的效果如下图所示:

如果你感兴趣的话,可以尝试着拖动下面示例中每个点的滑块,查看元素圆角的变化:

在CSS的世界中,属性取值是%值的话,他的计算是复杂的,而且不同的属性计算时相对物也有所不同,如果你对这方面感兴趣的话,建议你花一点时间阅读《CSS中百分比单位计算方式》一文。

border-radius嵌套会发生什么?

在Web开发过程中,在使用border-radius的时候,有的时候会产生圆角嵌套的视觉效果:

发生圆角嵌套的场景一般会出现在:

  • 带有边框的圆角场景
  • 带有内距的圆角场景
  • 同时带有边框和内距的圆角场景

比如:

<!-- HTML -->
<body>
    <div></div>
    <div></div>
    <div></div>
</body>

/* CSS */

body > div {
    margin: 5px;
    width: 480px;
    height: 480px;
    box-sizing: border-box;
    border-radius: 50px;
}

div:nth-child(1) {
    background-image: linear-gradient(134deg, #3023ae 0%, #c86dd7 100%);
    border: 30px solid #f90;
}

div:nth-child(2) {
    background-image: linear-gradient(134deg, #3023ae 0%, #c86dd7 100%),
        linear-gradient(134deg, #f90 0%, #f90 100%);
    background-origin: padding-box;
    background-clip: content-box, padding-box;
    padding: 30px;
}

div:nth-child(3) {
    background-image: linear-gradient(134deg, #3023ae 0%, #c86dd7 100%),
        linear-gradient(134deg, #f90 0%, #f90 100%);
    background-origin: padding-box;
    background-clip: content-box, padding-box;
    padding: 30px;
    border: 30px solid #f36;
}

效果如下:

正如上面示例所示,它们都产生了圆角嵌套的效果:

CSS的border-radius定义了元素外圆角,那么对于内圆角会有:

  • border-radius的值大于border-width时,会产生内圆角,并且内圆角的半么为border-radius - border-width
  • border-radius的值大于padding时,也会产生内圆角,并且内圆角的半么为border-radius - padding

上面这两种场景刚好对应的是上图中的左图和中间图,但除此之外,同时会出现border-radiusborder-widthpadding时,会产生多重内嵌套圆角,如上图最右侧效果。

会不会产生内圆角,则取决于:

  • 如果border-radius的值大于border-width时,则会产生内圆角
  • 如果border-radius的值大于padding时,则会产生内圆角
  • 如果border-radius的值和border-width的值(或padding值)相等或小于时,则不会产生内圆角

也就是说,当内圆角的产生时,内圆角的半径则是他们之间的差,即border-radius - border-widthborder-radius - paddingborder-radius - border-width - padding。比如上例:

border-radius - border-width = 50px - 30px = 20px
border-radius - padding = 50px - 30px = 20px
border-radius - border-width - padding = 50px - 30px - 30px = -10px 

你可以尝试着在下面的示例中调整border-radiuspaddingborder-width的值,查看元素圆角上的变化:

如果border-widthpadding每个方向取值不同,这个时候和border-radius产生的差值也将会不一样,也会出现内圆角的x轴和y的半径不同,内圆角的效果也将会类似于border-radius设置了xy轴的半径,比如border-radius: 10px / 20px。我们在上面的示例稍作调整,效果如下:

尝试着调整示例中的相关参数,你可以看到相关变化,比如下图所示,我将相应的圆角半径标注出来,从图中可以明白其中的变化:

从上图效果也不难发现,尽管border-width(或padding)以及同时设置这两个值,并且每个方向的值不同,如果border-radius和它们产生的差值大于0就会产生内部圆角,而且圆角的半径就等于其差值。

border-radius的半径重叠时会发生什么?

在Web中有一些UI的风格看上去就像“胶囊”的外形:

我们常把这种UI的风格称作“胶囊UI”,这种“胶囊UI”常用于一些buttoncheckboxradio的元素上。

CSS实现这种胶囊UI的效果,为了能达到一劳永逸,时常给元素的border-radius值设置为一个较大的值,比如999rem999vmax之类的。这样做不管元素高度是多少,都可以实现胶囊UI的效果:

拖动示例中右下角改变元素大小,但圆角效果是相同的(类似于胶囊):

示例中:

.pill {
    border-radius: 999vmax;
}

这行代码的意思,我想大家都懂,.pill元素四个角的“圆角”半径都是999vmax。这种方式很方便,因为这意味着我们不需要知道元素(矩形框)的尺寸,它也能正常的工作。不过,在某些边缘情况上,会遇到一些奇怪的行为。比如在上面的示例基础上稍作调整,就是把border-radius的值设置为:

.pill {
    border-radius: 100px 999vmax 999vmax 100px;
}

拖动右下角改变元素大小时,你会发现元素.pill左上角和右上角虽然设置了border-radius的半径值为100px,但并没有圆角效果:

那么问题来了:

border-radius: 100px 999vmax 999vmax 100px为什么左上角和左下角100px并没有生效?

为什么border-top-left-radius: 100pxborder-bottom-left-radius: 100px消失了?他们去哪了?

其实W3C规范中已经给出了答案

Let f = min(Li/Si), where i ∈ {top, right, bottom, left}, Si is the sum of the two corresponding radii of the corners on side i, and Ltop = Lbottom = the width of the box, and Lleft = Lright = the height of the box. If f < 1, then all corner radii are reduced by multiplying them by f.

具体的解释请看下图:

公式看上去令人感到困惑,甚至是令人头痛。但我们只需要记住一点:这个公式的目的是防止border-radius(圆角半径)重叠。简单地说:

客户端(浏览器)本质上是在想:“按比例缩小所有半径(border-radius),直到它们之间没有重叠”!

我们来用简单的示例来阐述上述公式的一些基本原理,这样可以让大家更好的理解。

首先,它会计算矩形(元素)每条边的长度与与它接触的半径之和的比值:

元素每条边宽度 / (相邻圆角半径1 + 相邻圆角半径2)

比如元素.pill设置的样式:

.pill {
    width: 600px;
    height: 200px;
    border-radius: 400px;
}

就该示例而言,按照上面示提供的公式就可以“计算出.pill元素每条边的长度与与它接触的半径之和的比率”:

然后将所有圆角的半径去乘以这些比值(每条边计算出来的比率值)中的最小值。上例中计算出来的比率值只有.75.25,取更小的值 .25,那么计算出来的圆角半径值则是:

400px x .25 = 100px

我们元素.pillheight200px最短的边长),计算出来的border-radius刚好是height的一半,即 100px。这也让我们实现了一个“胶囊”UI效果。

为了能了解的更清楚一些,我们回到前面有问题的示例中,只不过我们用400px来替代999vmax,比如:

.pill {
    width: 600px;
    height: 200px;
    border-radius: 100px 400px 400px 100px;
}

同样根据上面的公式来计算出每边的比例:

Ratio » 元素每条边宽度 / (相邻圆角半径1 + 相邻圆角半径2)

Top    » 600px / (100px + 400px)  = 1.2
Right  » 200px / (400px + 400px)  = 0.25
Bottom » 600px / (400px + 100px)  = 1.2
Left   » 200px / (100px + 100px)  = 1

四个方向最小的比率是 0.25,那么所有指定圆角半径乘以这个比例:

Top-Left     » 100px x 0.25 = 25px
Top-Right    » 400px x 0.25 = 100px
Bottom-Right » 400px x 0.25 = 100px
Bottom-Left  » 100px x 0.25 = 25px

这样一来,运用于.pill元素的border-radius值为25px 100px 100px 25px

是不是觉得非常的神奇。我想这部分内容能解答你平时碰到的一些怪异的现象,即 使用border-radius怪异的现象!

小结

CSS的border-radius已经存在很多年了,我想很多同学在平时的开发中都会使用它来实现一些圆角效果,而且还会使用它制作一些有趣的UI效果。但我想很多同学在使用border-radius会碰到一些怪异的现象,比如文章中圆角的嵌套,圆角重叠时按比例缩小等。最后希望这篇文章对大家有所帮助,如果你在使用border-radius时碰到其他一些怪异现象,欢迎在下面的评论中与我们共享。