border-radius的本质

前言

border-radius从名字上看像是指定border-box的圆角半径,实际上在使用时大家也是这么用的;但是从W3C官方文档定义来看,border-radius属性不仅会影响border-box的形状,还能影响padding-boxcontent-box的形状;

border-radius的本质就是通过定义盒模型四个顶点的圆角半径,从而确定四个顶点圆角的中心位置,四个中心点绘制的椭圆/圆分别对border-boxpadding-boxcontent-box进行裁剪

所以border-radius本质上是一种特殊的形状裁剪语法,和clip-path类似;

语法

border-radius本身是一种缩写属性,对应四个顶点的设置:

  • border-top-left-radius
  • border-top-right-radius
  • border-bottom-right-radius
  • border-bottom-left-radius

这种缩写方式与border属性类似,顶点顺序也是遵循顺时针方向,从左上到左下;

值的类型

  • <length>:绝对长度类型;
  • <percentage>:百分比类型,百分比基于半轴对应方向border-box的尺寸;

维度/方向

img

如果是在单个顶点设置圆角属性时,是以空格来分隔两个维度的半轴长度,前者为x方向半轴长度,后者为y方向半轴长度:

1
2
3
.demo {
border-bottom-left-radius: 50% 50px;
}

而在缩写语法中,通过斜杠(/来分隔不同方向的半轴长度,前面为x方向半轴长度的缩写,后面为y方向半轴长度的缩写;

缩写语法

border-radius缩写属性可接收1~4个值,不同数量的值会有不同的顶点分配:

  • 1个值:每个顶点具有一样的设置;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    .demo {
    border-radius: 10px;
    }

    /* or */

    .demo {
    border-radius: 10px / 5%;
    }
  • 2个值:第一个值对应左上和右下顶点,第二个值对应右上和左下顶点;可以看出这种语法是对角设置;

    1
    2
    3
    .demo {
    border-radius: 10px 20px;
    }
  • 3个值:第一个值对应左上顶点,第二个值对应右上和左下顶点(对角线),第三个值对应右下顶点;

    1
    2
    3
    .demo {
    border-radius: 10px 5% 20px;
    }
  • 4个值:四个值依次对应左上,右上,右下,左下四个顶点;

    1
    2
    3
    .demo {
    border-radius: 10px 30px 5% 11px;
    }

裁剪原理

以上关于语法的部分只不过是随处可见的资料,然而border-radius本质上是对盒模型进行形状裁剪,因此了解其裁剪原理才算是理解其本质;

注:圆是一种特殊的椭圆,下面用椭圆来替代圆/椭圆,不再赘述;

  1. 根据顶点圆角两个维度的半轴长度确定椭圆的中心点
  2. 从中心点画出椭圆弧,对相应的盒模型进行裁剪,裁剪后的形状就是新的盒模型形状(即可能不是正常的盒形);

img

从原理上来说是很简单的,但是其实还存在很多细节,一些边界情况如何处理等等;

确定中心点

border-box的中心点其实很好确定,毕竟border-radius指定的就是border-box顶点圆角的半轴长,根据border-box顶点位置和半轴长马上就能得到中心点位置;

Pcenter=Pborervertex(Rx, Ry)P_{center} = P_{borer-vertex} - (R_x,\ R_y)

其实其他盒模型的中心点的位置跟border-box的位置是一致的,关键在于半轴长度如何确定?毕竟border-radius只是指定了border-box圆角的半轴长度,因此其他盒模型的半轴长度只能基于此进行推导;

其他盒模型的半轴长

The padding edge (inner border) radius is the outer border radius minus the corresponding border thickness. In the case where this results in a negative value, the inner radius is zero.[1]

根据W3C文档的定义可知:

{Rpaddingx=RxWborderxRpaddingy=RyWbordery\begin{cases} R_{padding-x} = R_x - W_{border-x} \\[1em] R_{padding-y} = R_y - W_{border-y} \end{cases}\\[2em]

Rx,RyR_x,R_yborder-box的半轴长,WborderxW_{border-x}为水平方向的边框长度(顶点在左边就是左边框,在右边就是右边框),同理,WborderyW_{border-y}就是竖直方向的边框长度;

img

当然,如果得到的半轴长为负值时,会自动变成0,此时也就没有圆角裁剪效果了;同理可以得到content-box的圆角半轴长:

{Rcontentx=RpaddingxWpaddingxRcontenty=RpaddingyWpaddingy\begin{cases} R_{content-x} = R_{padding-x} - W_{padding-x} \\[1em] R_{content-y} = R_{padding-y} - W_{padding-y} \end{cases}\\[2em]

裁剪规则

上面为了图示方便画的都是一个完整的椭圆,实际上只需要四分之一个椭圆即可;具体是哪四分之一,需要根据顶点的方向来确定,即对应方向的象限(这里借用坐标象限表述可能清楚一些);

img

  • 左上:第二象限
  • 右上:第一象限
  • 右下:第四象限
  • 左下:第三象限

裁剪时椭圆曲线外被舍去,也可以理解为对应的椭圆曲线充当新的盒模型外曲线;以border-box为例:

img

img

其它顶点及盒模型裁剪也是类似的;

半轴长度的上限

从规范上来看,好像并没有提及半轴长度的上限,也就是只要半轴长度大于等于0理论上都是可行的;可是经过实践(blink内核和webkit内核),浏览器上面的表现可以看出半轴长度是存在一个上限的,这个上限就是盒模型相应方向尺寸的一半

当然,这并不是说当半轴长度超过盒模型相应方向尺寸的一半时,简单地按尺寸的一半进行处理;而是根据当前盒模型的宽高比进行一定的缩放,即把超出比例最多的方向的半轴长度设置为当前盒模型对应方向尺寸的一半,另一个方向按比例缩放

举个例子:一个元素的border-box是一个300x200box时,设置border-radius150px/200px,由于此时y方向半轴长度是超出比例最多的,因此会将y方向的半轴长度设置为box高度的一半——100px,那么此时另一个方向——x方向的半轴长度就是:

Rx=RyRyRx=100200150=75\begin{aligned} R'_x &= \frac{R'_y}{R_y} * R_x \\[1em] &= \frac{100}{200} * 150 \\[1em] &= 75 \end{aligned}

因此,此时border-radius: 150px/200px就等价于border-radius: 75px/100px

img

眼见为实

裁剪后的padding与border

从上面可以很明显的看出,经过border-radius的裁剪,border和padding也变形了;其实可以这么理解:

  • border-box外曲线与padding-box外曲线之间的区域就是border
  • 同理,padding-box外曲线与content-box外曲线之间的区域就是padding

案例分析

绘制蛋形

从鸡蛋形状可以分析:上半部形状较尖,下半部形状较圆;根据border-radius原理出发,想要绘制鸡蛋形状,上面两个顶点的椭圆肯定偏窄,下面两个顶点的的椭圆就圆一点;所谓偏窄就是y方向的半轴长度更大,得出css如下:

1
2
3
4
5
6
7
8
9
.egg {
width: 100px;
height: 150px;
border-top-left-radius: 50%;
border-top-right-radius: 50%;
border-bottom-left-radius: 50% 50px;
border-bottom-right-radius: 50% 50px;
background-color: gold;
}

img

胶囊分离动画

img

上面这个动画仅需一个元素,无需借助伪元素,如果理解了border-radius的本质,就很容易想明白的,可以挑战一下;

胶囊分离动画

相关文档


  1. CSS Backgrounds and Borders Module Level 3 ↩︎