三角形
div本身为矩形,但通过设置其border属性值,可将其转换为一个三角形。
向上的三角形及其变体
一步一步来。先为一个边长为50px的正方形设置宽度值为10px的边框。
body {
* {
display: inline-block;
margin: 1em;
}
> div {
width: 50px;
height: 50px;
border: 10px solid gray;
}
}
还是正方形的图形,仍未获得任何灵感。
第二步,将各边框设为不同的颜色。
body {
* {
display: inline-block;
margin: 1em;
}
> div {
width: 50px;
height: 50px;
border-top: 10px solid red;
border-left: 10px solid green;
border-right: 10px solid blue;
border-bottom: 10px solid mediumaquamarine;
}
}
边框与边框的交界处,各自占了一半的面积,从而出现了一条斜边。这些斜边将成为构建三角形的基本要素。
各种颜色的边框均为梯形,而非三角形。取青色的底边框为例,其高度不够,因此不能构成三角形。
小分支
当走到这一步,若将width及height值均设为0,并且等量加大各边框的厚度,则会得到另一个精美的图形。
body {
* {
display: inline-block;
margin: 1em;
}
> div {
width: 0px;
height: 0px;
border-top: 50px solid red;
border-left: 50px solid green;
border-right: 50px solid blue;
border-bottom: 50px solid mediumaquamarine;
}
}
这个图形以后可能会用得上。但在这里,还是回到原来的思路,继续往下走。
第三步,加大底边框的厚度值。
body {
* {
display: inline-block;
margin: 1em;
}
> div {
width: 50px;
height: 50px;
border-top: 10px solid red;
border-left: 10px solid green;
border-right: 10px solid blue;
border-bottom: 50px solid mediumaquamarine;
}
}
有点像三角形了。但梯形的顶边应从一条线段缩减为一个点。观察图形,梯形顶边的长度,正好就是div的width属性值。
故此,第四步,将div的width属性值设为0。
body {
* {
display: inline-block;
margin: 1em;
}
> div {
width: 0;
height: 50px;
border-top: 10px solid red;
border-left: 10px solid green;
border-right: 10px solid blue;
border-bottom: 50px solid mediumaquamarine;
}
}
下面的青色三角形已长成,但由于div的height属性值仍为50px,导致左边框的绿色及右边框的蓝色被撑高了。
第五步,将div的height属性值设为0。
body {
* {
display: inline-block;
margin: 1em;
}
> div {
width: 0;
height: 0;
border-top: 10px solid red;
border-left: 10px solid green;
border-right: 10px solid blue;
border-bottom: 50px solid mediumaquamarine;
}
}
红色的上边框占用了空间,故第六步,将设置border-top的代码删除。
body {
* {
display: inline-block;
margin: 1em;
}
> div {
width: 0;
height: 0;
border-left: 10px solid green;
border-right: 10px solid blue;
border-bottom: 50px solid mediumaquamarine;
}
}
此时,绿色的左边框与蓝色的右边框仍显示出来。观察青色的三角色的数据。div的border-bottom决定了三角形的高度,而三角形底边的宽度为左边框与右边框的宽度之和。因此,不能删除设置border-left及border-right的代码。
故此,第七步,将border-left及border-right的颜色值设置为transparent,让它们变成完全透明。并依视觉适当调整三角形底边的宽度及其高度。
body {
* {
display: inline-block;
margin: 1em;
}
> div {
width: 0;
height: 0;
border-left: 50px solid transparent;
border-right: 50px solid transparent;
border-bottom: 100px solid mediumaquamarine;
}
}
一个非常完美的等腰三角形出来了。
要绘制不等腰的三角形?只需将左边框与右边框的值设置为不一样的值即可。
body {
* {
display: inline-block;
margin: 1em;
}
> div {
width: 0;
height: 0;
border-left: 30px solid transparent;
border-right: 95px solid transparent;
border-bottom: 100px solid mediumaquamarine;
}
}
三角形不要放得那么正?旋转它。
body {
* {
display: inline-block;
margin: 1em;
}
> div {
width: 0;
height: 0;
border-left: 30px solid transparent;
border-right: 95px solid transparent;
border-bottom: 100px solid mediumaquamarine;
transform: rotate(45deg);
}
}
向下的三角形
左、右边框为透明,保留上边框,不要下边框。
body {
* {
display: inline-block;
margin: 1em;
}
> div {
width: 0;
height: 0;
border-top: 100px solid steelblue;
border-left: 50px solid transparent;
border-right: 50px solid transparent;
}
}
向右的三角形
上、下边框为透明,保留左边框,不要右边框。
body {
* {
display: inline-block;
margin: 1em;
}
> div {
width: 0;
height: 0;
border-top: 50px solid transparent;
border-bottom: 50px solid transparent;
border-left: 100px solid steelblue;
}
}
箭头
得到向右的三角形后,通过使用::before, ::after这两个伪类及绝对定位的机制,可扩展绘制出一个漂亮的箭头。
body {
padding: 2em;
div.arrow {
--arrow-body-width: 150px;
--arrow-body-height: 15px;
--arrow-head-width: 45px;
--arrow-head-height: 50px;
--arrow-color: skyblue;
outline: 1px solid gray;
width: calc(var(--arrow-body-width) + var(--arrow-head-width));
height: var(--arrow-head-height);
position: relative;
&::before {
content: '';
width: var(--arrow-body-width);
height: var(--arrow-body-height);
background-color: var(--arrow-color);
border-top-left-radius: 0.3em;
border-bottom-left-radius: 0.3em;
position: absolute;
top: calc(var(--arrow-head-height) / 2 - var(--arrow-body-height) / 2);
left: 0;
}
&::after {
content: '';
width: 0;
height: 0;
border-top: calc(var(--arrow-head-height) / 2) solid transparent;
border-bottom: calc(var(--arrow-head-height) / 2) solid transparent;
border-left: var(--arrow-head-width) solid var(--arrow-color);
position: absolute;
top: 0;
right: 0;
}
}
}
最外层通过outline绘制了一个不占用盒模型空间的灰色边框,以检查各个组件是否精准到位。将此边框擦除,即是一个干净、完整的箭头。
箭头的形状与颜色使用CSS变量来定义,只须改变5个变量值,即可得到形状各异的箭头。
应用clip-path来绘制
使用CSS Masking Module中的clip-path,我们就可以像Canvas, SVG一样,在CSS中通过调用相关的CSS函数直接绘制CSS Shapes Module所定义的基本几何图形了。并且,CSS函数提供了更为精细的控制。
circle
body {
--box-side: 80px;
--hue-value: 100;
div#container {
padding: 1em;
display: grid;
grid-template-columns: repeat(auto-fit, var(--box-side));
justify-content: center;
gap: 3em 0.5em;
> div {
aspect-ratio: 1 / 1;
background-color: hsl(var(--hue-value), 50%, 50%);
&:nth-child(1) {
clip-path: circle(calc(var(--box-side)/2) at center);
}
&:nth-child(2) {
clip-path: circle(calc(var(--box-side)/2) at left);
}
&:nth-child(3) {
clip-path: circle(calc(var(--box-side)/2) at right);
}
&:nth-child(4) {
clip-path: circle(calc(var(--box-side)/2) at top);
}
&:nth-child(5) {
clip-path: circle(calc(var(--box-side)/2) at bottom);
}
&:nth-child(6) {
clip-path: circle(calc(var(--box-side)/2) at top left);
}
&:nth-child(7) {
clip-path: circle(calc(var(--box-side)/2) at top right);
}
&:nth-child(8) {
clip-path: circle(calc(var(--box-side)/2) at bottom left);
}
&:nth-child(9) {
clip-path: circle(calc(var(--box-side)/2) at bottom right);
}
&:nth-child(2), &:nth-child(3), &:nth-child(4), &:nth-child(5) {
grid-row: 2;
background-color: hsl(calc(var(--hue-value) + 120), 50%, 50%);
}
&:nth-child(6), &:nth-child(7), &:nth-child(8), &:nth-child(9) {
grid-row: 3;
background-color: hsl(calc(var(--hue-value) + 240), 50%, 50%);
}
}
}
}
第一行绘制整个圆。第二行绘制半圆。第三行绘制四分之一的圆。
circle的第一个参数指定半径,at
之后的第二个参数指定在div的哪个位置建立起圆心。
ellipse
body {
--box-side: 80px;
--radius-length: calc(var(--box-side) / 2);
--hue-value: 150;
div#container {
padding: 1em;
display: grid;
grid-template-columns: repeat(auto-fit, var(--box-side));
justify-content: center;
gap: 3em 2em;
> div {
aspect-ratio: 1 / 1;
background-color: hsl(var(--hue-value), 50%, 50%);
&:nth-child(1) {
clip-path: ellipse(var(--radius-length) var(--radius-length) at center);
}
&:nth-child(2) {
background-color: hsl(calc(var(--hue-value) + 120), 50%, 50%);
clip-path: ellipse(calc(var(--radius-length) + 10px) var(--radius-length) at center);
}
&:nth-child(3) {
background-color: hsl(calc(var(--hue-value) + 120), 50%, 50%);
clip-path: ellipse(calc(var(--radius-length) + 10px) calc(var(--radius-length) - 10px) at center);
}
&:nth-child(4) {
background-color: hsl(calc(var(--hue-value) + 240), 50%, 50%);
clip-path: ellipse(50px 50px at center);
}
}
}
}
椭圆有长半径与短半径之分。当长半径等于短半径时,成为一个正圆。如第一个图形所示。
原则上可以自由取椭圆的长半径与短半径的值,只要它们大于0就行。但在这里,需注意的是我们是在一个div的裁剪路径中绘图,因此其图形受div的大小影响,超出div范围的绘制部分将被裁剪。
上面每个图形都是在div的中心建立起圆心,因此--radius-length正好等于边长的一半。第一个图形的长短半径均取值于--radius-length,即40px,故所绘制出来的图形完全在div的范围内,未产生裁剪,因此它得以全部显示出来为一个正圆。
第二个图形,X轴半径为50px,超出了正圆半径40px,而Y轴半径依旧正好为40px,则在水平方向上超出div区域的部分将被裁剪,故其左右两边露出了div的左、右边框。故而形成了一个水桶的形状。
第三个图形,在第二个图形的基础上,适当降低Y轴半径,则水桶的比例特征更明显。
第四个图形,X轴及Y半径均超出正圆半径一些,但又不能太多,以让圆弧落在div范围内,则四角出现了近乎直线的圆弧。
inset
inset函数用于指定缩进范围,以及指定圆角。
body {
--box-side: 80px;
--radius-length: calc(var(--box-side) / 2);
--hue-value: 150;
div#container {
padding: 1em;
display: grid;
grid-template-columns: repeat(auto-fit, var(--box-side));
justify-content: center;
gap: 3em 2em;
> div {
aspect-ratio: 1 / 1;
background-color: hsl(var(--hue-value), 50%, 50%);
&:nth-child(1) {
clip-path: inset(0px);
}
&:nth-child(2) {
clip-path: inset(20px 10px);
}
&:nth-child(3) {
clip-path: inset(10px 20px);
}
&:nth-child(4) {
clip-path: inset(10px 20px 30px 40px);
}
&:nth-child(5) {
background-color: hsl(calc(var(--hue-value) + 120), 50%, 50%);
grid-row: 2;
clip-path: inset(0px round 50%);
}
&:nth-child(6) {
background-color: hsl(calc(var(--hue-value) + 120), 50%, 50%);
grid-row: 2;
clip-path: inset(0px round 30% 20%);
}
&:nth-child(7) {
background-color: hsl(calc(var(--hue-value) + 120), 50%, 50%);
grid-row: 2;
clip-path: inset(0px round 80% 20%);
}
&:nth-child(8) {
background-color: hsl(calc(var(--hue-value) + 120), 50%, 50%);
grid-row: 2;
clip-path: inset(0px round 10% 20% 30% 40%);
}
&:nth-child(9) {
background-color: hsl(calc(var(--hue-value) + 240), 50%, 50%);
grid-row: 3;
clip-path: inset(0px round 50% / 10% 20% 30% 40%);
}
}
}
}
第一行仅缩进。该行上的第一个图形的参数为0px,则没有任何缩进;第二个及第三个分别在垂直及水平方向上缩进了不同的数值。第四个图形的参数为4个数值,分别对应上、右、下、左的缩进量。
第二行及第三行仅指定圆角。该行上的第一个图形使用round,指定了4个圆角均为50%,则为正圆。第二个图形的round有2个参数,分别对应左上、右下以及右上、左下的值。第三个图形参数个数一样,但第1个参数值较大,则出现了椭圆的效果。第四个图形使用了4个参数,分别指定了左上、右上、右下、左下的圆角。
每个圆角均以椭圆的方式来定义,因此均可分别指定每个圆角在X轴上的半径及在Y轴上的半径。第三行的图形,先将4个圆角在X轴上的半径统一定义为50%,然后以/
符号为分隔符,将4个圆角在Y轴上的半径分别按左上、右上、右下、左下的顺序予以指定。
xyhw
xyhw指定一个具有偏移坐标值及宽度、长度值的矩形。
body {
--box-side: 100px;
--radius-length: calc(var(--box-side) / 2);
--hue-value: 170;
div#container {
padding: 1em;
display: grid;
grid-template-columns: repeat(auto-fit, var(--box-side));
justify-content: center;
gap: 3em 2em;
> div {
aspect-ratio: 1 / 1;
background-color: hsl(var(--hue-value), 50%, 50%);
&:nth-child(1) {
grid-row: 1;
grid-column: 1;
border: 1px dashed gray;
background-color: rgb(66 66 66 / 0.5);
}
&:nth-child(2) {
grid-row: 1;
grid-column: 1;
background-color: hsl(calc(var(--hue-value)), 50%, 50%);
clip-path: xywh(
calc(var(--box-side)/4) calc(var(--box-side)/4) 50% 50%
round 0.5em
);
}
}
}
}
rect
rect以为各边框分别指定缩进的偏移值来指定一个矩形。
body {
--box-side: 100px;
--radius-length: calc(var(--box-side) / 2);
--hue-value: 170;
div#container {
padding: 1em;
display: grid;
grid-template-columns: repeat(auto-fit, var(--box-side));
justify-content: center;
gap: 3em 2em;
> div {
aspect-ratio: 1 / 1;
background-color: hsl(var(--hue-value), 50%, 50%);
&:nth-child(1) {
grid-row: 1;
grid-column: 1;
border: 1px dashed gray;
background-color: rgb(66 66 66 / 0.5);
}
&:nth-child(2) {
grid-row: 1;
grid-column: 1;
background-color: hsl(calc(var(--hue-value)), 50%, 50%);
clip-path: rect(0% 100% 100% 0%);
}
&:nth-child(3) {
grid-row: 1;
grid-column: 2;
border: 1px dashed gray;
background-color: rgb(66 66 66 / 0.5);
}
&:nth-child(4) {
grid-row: 1;
grid-column: 2;
background-color: hsl(calc(var(--hue-value) + 180), 50%, 50%);
clip-path: rect(10% 80% 70% 20%);
}
}
}
}
rect函数有4个参数,分别对应于顶边框、右边框、底边框、左边框的缩进偏移值。
但参考边框只有顶边框及左边框两个。这意味着第1个参数及第3个参数是距离顶边框的偏移值,第2个参数及第4个参数是距离左边框的偏移值。
青色图形的第1个参数为0%,代表其顶边框的偏移值,即离顶边框的距离为0%,因此正好与原来的顶边框齐平;第3个参数为100%,代表其底边框的偏移值,即离顶边框的距离为100%,因此正好与原来的底边框齐平。
同样,青色图形的第2个参数为100%,代表其右边框的偏移值,即离左边框的距离为100%,因此正好与原来的右边框齐平;第4个参数为0%,代表其左边框的偏移值,即离左边框的距离为0%,因此正好与原来的左边框齐平。
因此,青色图形正好将原来的图形完全盖住了。
而红色图形的4个参数,分别意味着,顶边框比原来的顶边框偏移10%,右边框比原来的左边框偏移80%,下边框比原来的顶边框偏移70%,左边框比原来的左边框偏移20%。
polygon
polygon根据所提供的各个点,绘制一个封闭的多边形。
body {
--box-side: 80px;
--radius-length: calc(var(--box-side) / 2);
--hue-value: 150;
div#container {
padding: 1em;
display: grid;
grid-template-columns: repeat(auto-fit, var(--box-side));
justify-content: center;
gap: 3em 2em;
> div {
aspect-ratio: 1 / 1;
background-color: hsl(var(--hue-value), 50%, 50%);
&:nth-child(1) {
grid-row: 1;
grid-column: 1;
border: 1px dashed gray;
background-color: rgb(66 66 66 / 0.5);
}
&:nth-child(2) {
grid-row: 1;
grid-column: 1;
background-color: hsl(calc(var(--hue-value)), 50%, 50%);
clip-path: polygon(nonzero, 0 0, 100% 0, 50% 80px);
}
&:nth-child(3) {
grid-row: 1;
grid-column: 2;
border: 1px dashed gray;
background-color: rgb(66 66 66 / 0.5);
}
&:nth-child(4) {
grid-row: 1;
grid-column: 2;
background-color: hsl(calc(var(--hue-value)), 50%, 50%);
clip-path: polygon(50% 0, 0 50%, 50% 100%, 100% 50%);
}
}
}
}
polygon的第1个参数可选,用于指定填充规则 (filling rule),默认值为nonzero,另一可选值为evenodd,具体详见Canvas Fill Rule。
第2个参数是由多个(x, y)坐标值所组成的一组数值。每组坐标值之间、各组坐标值之间,可用空格或,
号隔开。
坐标值是相对于div左上角的偏移值,当为非零值时,数值后面须带有明确的CSS单位,如10px, 50%, 3em等等。
一般使用polygon来绘制由直线组成的多边形,若需要绘制弧线,应使用path。
尽管CSS Shapes Module Level 1规范了polygon有一可选参数round
,但Safari及Chrome目前尚未支持。
path
path绘制曼妙多姿的各种弧线。
body {
--box-side: 300px;
--radius-length: calc(var(--box-side) / 2);
--hue-value: 90;
div#container {
padding: 1em;
display: grid;
grid-template-columns: repeat(auto-fit, var(--box-side));
justify-content: center;
gap: 3em 2em;
> div {
aspect-ratio: 1 / 1;
background-color: hsl(var(--hue-value), 50%, 50%);
&:nth-child(1) {
grid-row: 1;
grid-column: 1;
border: 1px dashed gray;
background-color: rgb(66 66 66 / 0.5);
}
&:nth-child(2) {
grid-row: 1;
grid-column: 1;
background-color: hsl(calc(var(--hue-value)), 50%, 50%);
clip-path: path(nonzero, "M 134,25 C 9,152 240,184 205,233 C 38,141 280,97 60,229 C 61,130 100,86 152,98 S 202,52 165,45 Z");
transform: translate(20px, 15px);
}
}
}
}
path的第1个参数可选,用于指定填充规则 (filling rule),默认值为nonzero,另一可选值为evenodd,具体详见Canvas Fill Rule。
第2个参数是一个字符串,使用SVG的path命令格式。字符串的末尾如果没有关闭图形的z命令时,浏览器将自动添加,以自动关闭图形。
shape
path函数存在着一些先天不足。
一是命令及参数都以字符串的形式存在,因此我们需自己构建此字符串(本站的SVG Path Editor提供了相应的功能)。故此,它不能直接使用CSS变量。
二是默认只能使用px作为单位,因此在构建自动感应 (auto responsive) 的图形时,无法使用%的单位来保持图形的整体比例。
为解决这些问题,shape函数应运而生。它自带一套与SVGpath标签的d属性相对应的子命令;支持CSS变量;支持各种各样的CSS单位,绘制贝塞尔曲线时统一归置控制点,等诸多优势。
body {
--box-side: 200px;
--radius-length: calc(var(--box-side) / 2);
--hue-value: 90;
div#container {
padding: 1em;
display: grid;
grid-template-columns: repeat(auto-fit, var(--box-side));
justify-content: center;
gap: 3em 2em;
> div {
aspect-ratio: 1 / 1;
background-color: hsl(var(--hue-value), 50%, 50%);
&:nth-child(1) {
grid-row: 1;
grid-column: 1;
border: 1px dashed gray;
background-color: rgb(66 66 66 / 0.5);
}
&:nth-child(2) {
grid-row: 1;
grid-column: 1;
background-color: hsl(calc(var(--hue-value)), 50%, 50%);
clip-path: shape(
from 5% 0%,
hline to 95%,
curve to right 5% with right top,
vline to 92%,
curve to 95% 97% with right 97%,
hline to 70%,
line by -2% 3%,
line by -2% -3%,
hline to 5%,
curve to left 92% with left 97%,
vline to 5%,
curve to 5% top with left top
);
}
}
}
}
上面的shape函数,较多使用了%作为CSS单位,则在我们通过CSS变量--box-side修改div的尺寸大小时,所绘制的图形自动按比例缩放。
from子命令用于确定当前点的位置。
curve to子命令使用了right, top等关键字来表示位置,非常直观;with后面则紧随控制点的信息,整洁有序。
使用to来表示绝对定位,使用by来表示相对定位。