CSS艺术 绘制形状

椭圆

border-radius 可以指定数值或百分比,当使用百分比的时候,可以让 border 按各自边长计算圆角,实现椭圆

1
2
3
4
5
6
.box {
width: 200px;
height: 100px;
background: goldenrod;
border-radius: 50%;
}

半橢圓

border-radius 是一个简写的属性, 它的完整属性可以表述四个角的圆角

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

属性的两个长度或百分比值定义了椭圆的四分之一外边框的边缘角落的形状。第一个值是水平半径,第二个是垂直半径。如果省略第二个值,它是从第一个复制。如果任一长度为零,角落里是方的,不圆润。水平半径的百分比是指边界框的宽度,而垂直半径的百分比是指边界框的高度。

这样我们只需要指定上边两个角或下边两个角的圆角即可

1
2
3
4
5
6
7
.box {
width: 200px;
height: 100px;
background: goldenrod;
border-top-left-radius: 50% 100%;
border-top-right-radius:50% 100%;
}

可以是使用简写的属性, border-radius 可以用 / 分隔两组值,左边代表四个角的水平半径,右边代表垂直半径

而且不同的个数代表不同的位置,这与 border 类似

50% top-left:50% | top-right:50% | bottom-right:50% | bottom-left:50%
50% 40% top-left:50% | top-right:40% | bottom-right:50% | bottom-left:40%
50% 40% 30% top-left:50% | top-right:40% | bottom-right:30% | bottom-left:40%
50% 40% 30% 20% top-left:50% | top-right:40% | bottom-right:30% | bottom-left:20%

所以分析一下这个半椭圆

  • 水平方向上面的两个角是 50%, 暂时可以写为 50% 50% 0 0 / xx xx xx xx
  • 垂直方向上面两个角是 100%,现在变为 50% 50% 0 0 / 100% 100% 0 0
  • 因为半椭圆的垂直方向占据了整个元素的高度,所以不能使用简写属性, 必须要指定上面两垂直半径是100%, 这样弧度才会从底部延伸到顶部
    现在垂直半径后两个为0,意味着对应的水平半径即使给了也不会生效,因为不能只通过一个半轴长度画椭圆,最终能够属性会变为 50% / 100% 100% 0 0
1
2
3
4
5
6
.box {
width: 200px;
height: 100px;
background: goldenrod;
border-radius: 50%/ 100% 100% 0 0;
}

同理如果你想画一个垂直方向的半椭圆

1
2
3
4
5
6
.box {
width: 200px;
height: 100px;
background: goldenrod;
border-radius: 100% 0 0 100%/50%;
}

1/4 椭圆也是同样的道理,只需指定一个角上的半径

1
2
3
4
5
6
.box {
width: 200px;
height: 100px;
background: goldenrod;
border-radius: 100% 0 0 0;
}

这个网址里你可以看到各种通过圆角制作的精美按钮

平行四边形/菱形

你可能很容易想到使用 skew, 但是有一些细节需要注意, 如果 skew 作用在一个有文字的元素上, 那么里面的文字也会被拉伸

想解决这个问题, 可能会想到使用两个元素嵌套, 让里面的元素使用反向 skew, 让文字重新边正

有没有一种方式,可以不嵌套元素,还能让文字不受影响, 办法就是使用 伪元素, 因为伪元素和元素本身不属于嵌套关系,所以更容易处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
.el {
position: relative;
/* 写文字相关的样式 */
}

/* 写背景形状相关的样式 */
.el::after {
content: "";
position: absolute;
left:0;
top:0;
right:0;
bottom: 0;
/* 放在文字元素下面 */
z-index: -1;
}

对于菱形, 是四边相等的平行四边形, 最容易想到的就是旋转一个正方形

1
2
3
width: 100px;
height: 100px;
transform: rotate(45deg);

但是中心线长度不相等平行四边形会遇到一点麻烦, 最核心的一个问题就是, 拉伸后的高度应该等于宽度, 以下面这个 宽为100,高为40 的长方形为例

想求的是 AGF 的角度, 那么只要求出 FGE 就可以了, sinFGE = AG / FG 所以 FCE = srcsin(40/100)

再把 FCE 转成角度 FCE = srcsin(40/100) * 360 / (2 * PI) = 23.5781(deg)

那么 AGF = (90 - 23.57)deg

1
2
3
width: 100px;
height: 40px;
transform: skew(-66.43deg);

菱形剪裁

有时候希望一张图片能剪裁成菱形的形状, 我们已经知道菱形如何制作,所以很容易想到用两个元素嵌套

第一步把外层的元素旋转并处理成菱形, 里面的元素反向旋转修正, 又因为拉伸之后图片的上下可能填不满,所以需要通过缩放填满外层元素

1
2
3
<div id='box'>
<img />
</div>
1
2
3
4
5
6
7
.box {
transform: rotate(-78deg) skew(-66.43deg);
overflow: hidden;
}
.box img{
transform: skew(66.43deg) scale(3);
}

除了这种比较传统的方法, 现在我们有了一个新的属性可以完成这个效果 clip-path, 可以指定点的位置并链接成图形, 如果使用百分比会按照自身的尺寸解析

1
2
3
4
5
6
7
8
9
10
11
img{
width: 320px;
height: 180px;
clip-path: polygon(50% 0,100% 50%,50% 100%,0 50%);
transition: 1s clip-path;
}

img:hover {
clip-path: polygon(0 0,100% 0,100% 100%,0 100%);

}

切角效果

看过背景与边框一章之后,很容易会想到用渐变的方式是来实现,另外通常情况下会考虑使用scss来处理

1
2
3
4
5
background: #5a8;
background: linear-gradient(-45deg, transparent 20px, #5a8 0) right,
linear-gradient(45deg, transparent 20px, #fd2 0) left;
background-size: 50% 100%;
background-repeat: no-repeat;

1
2
3
4
5
6
7
8
/* hack */
background: #5a8;
background: linear-gradient(-135deg, transparent 20px, #5a8 0) top right,
linear-gradient(135deg, transparent 20px, #542 0) top left,
linear-gradient(-45deg, transparent 20px, #fd2 0) bottom right,
linear-gradient(45deg, transparent 20px, #e93 0) bottom left;
background-size: 50% 50%;
background-repeat: no-repeat;

1
2
3
4
5
6
7
8
background: #5a8;
background:
radial-gradient(circle at top right , transparent 20px, #5a8 0) top right,
radial-gradient(circle at top left,transparent 20px, #542 0) top left,
radial-gradient(circle at bottom right, transparent 20px, #fd2 0) bottom right,
radial-gradient(circle at bottom left, transparent 20px, #e93 0) bottom left;
background-size: 50% 50%;
background-repeat: no-repeat;

上面的方法算是比较完美的解决了这个问题,其中有一点不足就是代码量比较多,可能难以维护

还可以换一个思路,使用 svg + border-image 这种解决方案, svg 当作边框背景, 创造一个可以被九宫格分割的svg图片, 让九宫格的四个角为折角就能实现我们的需求

有几个细节需要注意一下, fill 属性需要编码, 需要添加 background-clip 属性,否则背景颜色会延伸到边框区域, 添加一个 border 属性用于hack, 在 border-image 不支持的时候可以回退

1
2
3
4
5
6
7
8
border: 20px solid #58a;
height: 140px;
background-clip: padding-box;
background: #58a;
border-image: 1 url('data:image/svg+xml,\
<svg xmlns="http://www.w3.org/2000/svg" width="3" height="3" fill="%2358a">\
<polygon points="0,1 1,0 2,0 3,1 3,2 2,3 1,3 0,2"/>\
</svg>');

梯形

从上面的平行四边形中可能会受到一点启发,但实际上在二维变化中,没有一种办法可以将矩形或其他图形,转换成梯形.

也许可以想到利用两个伪类实现梯形两边,但是一旦需要添加边框或圆角, 这种中方案立刻就没有了操作性.

既然二维不行,可以思考一下三维中的实现办法, 可以利用透视让矩形的一条边远离我们,从而在视觉上实现梯形的效果.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
.box {
position: relative;
width: 200px;
height: 60px;
line-height:60px;
font-size: 20px;
color: #fff;
text-align: center;
}
.box:after {
content: "";
position: absolute;
left:0;
top:0;
bottom: 0;
right: 0;
margin: auto;
z-index: -1;
border-radius: 8px;
border: 2px solid darkorchid;
background: #58a;
/* 保持底边固定,整个图形围绕底边旋转 */
transform-origin: bottom;
/* 第一个属性是景深, 用于表现出3D效果,经过空间旋转的矩形在视觉上高度会缩小, 所以通过放大高度来使变换后的图形和之前的图形,高度相同 */
transform:perspective(300px) rotateX(30deg) scaleY(1.25);
}

当需要只有一边倾斜的梯形是,只需要修改修改变换中心. 这个中心可以理解为视觉中是一个直角坐标系, 这个中心点永远在你视线的正前方.
当变换中心设置为 bottom 的时候,相当于把这个元素的底边放在了视线中心上,但是左右两边会被视线中心平分, 所以元素绕 x 轴转动的时候,左右两边因为透视会向中间收缩.
当变换中心设置为 bottom ,left 的时候, 除了底边在视线中心上,左边也在视线中心, 所以旋转的时候,左边只有高度的变化,而不会因为透视,向中间收缩,因为这条边垂直与你的视线.

1
2
3
4
.box:after {
transform-origin: bottom left;
transform: perspective(91px) rotateX(18deg) scaleY(1.25);
}

饼图

先思考一下实现一个双色的饼图需要几个元素, 其实两个元素就够了,其中一个是伪元素, 实现思路是把元素的背景色设置成渐变的两半,伪元素大小为元素的一半,这样就可以把底色漏出来,而显示进度的那一半颜色可以用伪元素覆盖住.

通过旋转伪元素,并切换伪元素的颜色来显示饼图的大小, 说起来简单但是实现起来细节很多

先来实现一个 20% 的饼图, 这里用到了 turn 这个表示圈的单位, 0.2turn 表示的就是 0.2 * 360deg, 可以让你免于计算角度

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
.box{
position: relative;
width: 200px;
height: 200px;
background: yellowgreen;
background-image: linear-gradient(90deg, transparent 50%,#655 0);
border-radius: 50%;
overflow: hidden;
}
.box::after {
content:'';
position: absolute;
width: 50%;
height: 100%;
border-radius: 0 100% 100% 0 / 50%;
left: 50%;
top:0;
background-color: inherit;
transform-origin: left center;
transform: rotate(0.2turn);
}

但是当角度超过 50% 就会有一些问题,因为伪类的颜色还是和没有占比区域的颜色相同,所以还没法表现超出 50% 的饼图, 第一步需要修改伪类的颜色

但是伪类已经旋转了 180deg,仅仅改变颜色会和另一半颜色拼在一起显示出一个 100% 的饼图,所以需要减去半圈 0.5turn, 如果表示 70% 只需要旋转 (0.7turn - 0.5turn) = 0.2turn 就够了

1
2
3
4
5
6
7
8
9
10
11
12
13
.box{
position: relative;
width: 200px;
height: 200px;
background: yellowgreen;
background-image: linear-gradient(90deg, transparent 50%,#655 0);
border-radius: 50%;
overflow: hidden;
}
.box::after {
background-color: #665;
transform: rotate(0.2turn);
}

到这里似乎已经可以实现饼图的效果了,但如果想修改一个比例,我们能会修改颜色,修改圈数,能不能只通过一个属性就控制为元素的颜色和角度,这里会用到很多 animation 相关的属性

第一点需要解决如何让超过 50% 之后,颜色自动改变,可能只用 animation 有这个能力,因为没有什么选择器可以判断元素是不是旋转过了一半,而 animation 可以控制动画的执行位置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
.box{
position: relative;
width: 200px;
height: 200px;
background: yellowgreen;
background-image: linear-gradient(90deg, transparent 50%,#655 0);
border-radius: 50%;
overflow: hidden;
}
.box::after {
content:'';
position: absolute;
width: 50%;
height: 100%;
border-radius: 0 100% 100% 0 / 50%;
left: 50%;
top:0;
background-color: inherit;
transform-origin: left center;
animation: bg 100s step-end infinite,ani 50s linear infinite;
animation-delay: -10s;
animation-play-state: paused;
}
@keyframes bg {
50% {
background: #655;
}
}

@keyframes ani {
to {
transform: rotate(0.5turn);
}
}

step-end 的目的就是在动画指定到一半也就是 50s 的时候,颜色突然改变,而这时也恰好旋转了半圈因为 ani 动画的执行时间是 50s 旋转半圈, 另外需要使用 animation-play-state: paused 把动画暂停住,这样在一个合适的角度就能显示出比例

这里用到了 animation-delay 很少使用到的属性, 一个负的延时时间,这是有意义的,它的行为与 0s 延时类似,都会立即执行动画,但是一个负值表示动画已经开始播放,并且持续了对应的时间,效果就是显示第一帧的时候,好像动画已经播放了这么长时间.所以指定一个负值来表示已经旋转过的角度. 如果是 70% 那可以设置为 animation-delay: -70s

打赏
  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2015-2025 SunZhiqi

此时无声胜有声!

支付宝
微信