前言
本文为《CSS揭秘》的第二部分-形状的笔记,介绍了自适应椭圆、平行四边形、棱形图片、切角效果、梯形标签页、简单饼图的实现原理,并在每节中附上了个人的代码链接。文中对各类形状的实现给出了多种方案,不得不感慨新属性(如clip-path、conic-gradient)的出现给我们带来的便捷,其中conic-gradient在实现饼图上简直绝了,强烈推荐~
以下是一张总体概要的脑图,方便帮助大家进行知识梳理。其中标♥的部分为一些比较妙的方案,可以重点学习。
1.自适应椭圆
背景知识
border-radius 属性的基本用法
原理
border-radius的三个小知识:
- border-radius可以单独指定水平和垂直半径,只要用一个斜杠( / )分隔这两个值即可;
- border-radius不仅可以接受长度值,还可以接受百分比值;
- border-radius是这个简写属性,他可以写成以空格分开的四个值,这四个值分别从左上角开始以顺时针顺序应用到元素的各个拐角。
1 | border-radius: 水平左上 水平右上 水平右下 水平左下 / |
案例
有了上面的知识我们可以轻松搞定自适应的椭圆。以下代码详情点此处。
1.自适应椭圆:
利用百分比值会基于元素的尺寸进行解析,从而达到自适应的目的。
1 | .full { |
2.x轴方向左半椭圆
这个形状,水平方向上,左边的两个圆角占据了整个元素的宽度,而且右边没有圆角,因此在水平方向border-radius的值为100% 0 0 100%;垂直方向上,左上角垂直半径和左下角的垂直半径相同,都为50%,同时因为右边两个圆角水平方向的border-radius为0,故右边两个圆角垂直方向的border-radius已经不重要了,可以为任意值。
1 | .x-semi { |
3.四分之一椭圆
1 | .quarter { |
2.平行四边形
背景知识
CSS 变形 skew
要生成平行四边形,我们可以很自然地想到利用transform的skew属性来对矩形进行斜向拉伸。
1 | transform: skewX(-45deg); |
但是这样做,导致平行四边形内的内容也发生了斜向变形。下面提供两种只让容器的形状倾斜,而保持其内容不变的方案。
嵌套元素方案
嵌套一层DOM元素,再对内容进行一次反向的 skew() 变形。
核心代码如下:(代码链接)
1 | <div class="parallelogram-1"> |
1 | .parallelogram-1 { |
缺点: 需要用两层DOM标签
伪元素方案
把所有样式(背景、边框等)应用到伪元素上,然后再对伪元素进行变形,因为我们的内容并不是包含在伪元素里的,所以内容并不会受到变形的影响。此时,用伪元素生成的方块是重叠在内容之上的,一旦给它设置背景,就会遮住内容,我们可以给伪元素设置z-index: -1 样式,这样它的堆叠层次就会被推到宿主元素之后。
最终代码如下:(代码链接)
1 | .parallelogram-2 { |
缺点: 变形后导致事件的点击区域与看见的实际元素区域不一致
3.棱形图片
背景知识
CSS 变形,“平行四边形”
基于变形的方案
基于“平行四边形”中讨论的第一个解决方案,需要把图片用一个div标签包裹起来,然后对其应用相反的 rotate()变形样式,代码如下:
1 | <div class="diamond-1"> |
1 | .diamond-1 { |
我们想要得到上面图片右边的效果,但实际却得到了左边的效果。主要问题在于 max-width: 100% 这条声明。 100% 会被解析为容器(.diamond-1)的边长,但是我们想让图片的宽度与容器的对角线相等,对角线的长度为边长的$√2≈ 1.42$,因此我们可以设置max-width为142%,但此时效果如下图所示,因为通过 width 属性来放大图片时,只会以它的左上角为原点进行缩放。
最终我们可以利用 scale() 变形样式来把这个图片放大1.42倍来达到效果,最终代码如下:(代码链接)
1 | .diamond-1 { |
缺点: 需要用两层DOM标签
裁切路径方案
使用新属性clip-path来对图片进行裁剪,但目前这个属性浏览器的支持程度还有限。
1 | .diamond-2 { |
除了浏览器支持有限外,这个属性能创造的奇迹还不止于此,它还能通过polygon()函数裁剪出各种多边形,通过circle()函数裁剪出圆,通过ellipse()函数裁剪出椭圆。
4.切角效果
背景知识
CSS 渐变, background-size ,“条纹背景”,border-image, clip-path
CSS渐变方案
切一个角效果: 以右下角为例。我们可以只需要一个线性渐变就可以达到目标。这个渐变需要把一个透明色标放在切角处,然后在相同位置设置另一个色标,并且把它的颜色设置为我们想要的背景色。(代码链接)
1 | background: #58a; |
注意:事实上,第一行声明并不是必需的,加上它是将其作为回退机制:如果某些浏览器不支持 CSS渐变,那第二行声明会被丢弃,而此时我们至少还能得到一个简单的实色背景。
切两个角效果: 以左下角和右下角切掉为例。我们需要把这个图形看成两部分,从中间分成两半,右边使用一个右下切角的线性渐变,左边使用一个左下切角的线性渐变,同时需要把background-repeat关掉,代码如下:
1 | background: #58a; |
切四个角效果: 明白了切两个角的原理,切四个角怎么难得到机智的我们。代码如下:
1 | background: #58a; |
弧形切角: 弧形切角原理通切四个角的效果,只不过把线性渐变变为径向渐变。代码如下:
1 | background: #58a; |
内联 SVG 与 border-image 方案
在常规设计中,四个角的切角尺寸往往是一致的。由于border-image会解决缩放问题,而 SVG 可以实现与尺寸完全无关的完美缩放,将以下的svg应用于border-image,并设置border-image-slice为1,如虚线标注的切片方式,便可以得到一个切角效果。
1 | <svg xmlns="http://www.w3.org/2000/svg" width="3" height="3" fill="#58a"> |
切角代码如下:
1 | border: 15px solid transparent; |
通过以上代码,我们发现两个问题,一个是背景图片的问题,一个是切角尺寸比原来小的问题。
我们先来解决背景图片的问题。要么给 border-image 属性值在border-image-slice后加上 fill 关键字——这样它就不会丢掉SVG中央的那个切片了;或者给他指定一个背景色,并且设置background-clip:padding-content,这样还能发挥一个回退的作用。
切角尺寸比原来小的原因,请看下图。原来通过渐变生成的切角,15px是沿着渐变轴来度量的,也就是下图中的斜向距离,而通过此方法用到的15px是边框的宽度,也就是下图的水平或者垂直距离,故要使斜向距离达到15px,border的宽度应为$15×√2≈ 21.213$,近似取20。
另外,我们给边框加一个背景颜色,当border-image属性不被浏览器支持时提供一个回退方案。所以最终代码如下(代码链接):
1 | border: 20px solid #58a; /* 用背景色而不是transparent是为了在浏览器不支持border-image时,提供回退方案 */ |
裁切路径方案
1 | background: #58a; |
三种方案的对比
5.梯形标签页
背景知识
基本的 3D 变形,“平行四边形”
类似于平行四边形那一节学到的方法,我们给伪元素做3D变形,代码如下:
1 | .trapezoid { |
只给元素做3D变得到的结果为上图1所示,图2为图1变形前后的对照图,我们可以看出默认变形中心transform-orign在元素自身的中心线上,经过旋转,元素的宽度增加,梯形占据的位置会稍微下移,高度也会有少许缩减。而文章中提到一种方法如图3所示,把变形中心transform-orign设为bottom,这时我们只要补偿高度,而这个垂直方向上的缩放程度大概为130%左右,于是得到最终代码,如下:
1 | .trapezoid { |
于是得到一个漂亮的梯形。
既然我们是要生成梯形标签,我们根据上述方案,就能轻而易举地得到如下梯形标签tab样式,由于篇幅原因,代码请点此处。
6.简单的饼图
背景知识
CSS 渐变,基本的 SVG,CSS 动画,“条纹背景”,“自适应的椭圆”,conic-gradient
我们案例用黄绿色(yellowgreen)表示底色,并采用棕色(#655)来显示比率。
基于transform的方案
原理是这样的(篇幅比较长),我们先利用linear-gradient把圆分为两部分,然后再用伪元素构造成一个半圆盖上去,通过旋转伪元素来决定露出多大的扇区。
先构造一个两种颜色的圆,代码如下:
1 | .pie { |
然后再利用伪元素构造一个半圆盖在此元素上,并把旋转中心设置在圆心。
1 | pie::before { |
若要得到一个显示率为0-50%的饼图,只需把伪元素背景设为黄绿色,然后旋转相应的度数,我们以20%为例,旋转
$ 360deg×0.2 = 72deg = 0.2turn $ (注:turn为CSS3角度单位,表示圈,1圈为360deg )。
1 | pie::before { |
若要得到一个显示率大于50%的饼图,只需把伪元素背景设为棕色,然后旋转相应的度数,我们以60%为例,旋转
$ 360deg×(0.6-0.5) = 36deg = 0.1turn $。
1 | pie::before { |
知道了原理,我们接下来需要把这两种情况封装一下。我们先来看一个动画,以下代码实现了一个饼图从 0 变化到 100% 的动画。
1 | @keyframes spin { |
再来看一个关于animation-delay的规范。
“一个负的延时值是合法的。与 0s 的延时类似,它意味着动画会立即开始播放,但会自动前进到延时值的绝对值处,就好像动画在过去已经播放了指定的时间一样。因此实际效果就是动画跳过指定时间而从中间开始播放了。”
——CSS 动画(第一版)(http://w3.org/TR/css-animations/#animation-delay)
在了解了以上动画和animation-delay的规范后,我们将使用上面的动画来解决这个封装,但动画必须处于暂停状态,且必须暂停在我们想要的位置。我们要用负的动画延时来直接跳至动画中的任意时间点,并且定格在那里。
我们设置动画的总时长为100s,我们可以用内联样式的方式为其设置 animation-delay 属性,然后再在伪元素上应用 animation-delay: inherit属性。综合以上要素,如果要让饼图显示为 20% 和 60%,则html结构代码为:
1 | <div class="pie" style="animation-delay: -20s"></div> |
进一步优化html代码结构,优化后代码如下:
1 | <div class="pie">20%</div> |
这时,我们采用一段js脚本来把animation-delay 写到内联样式中,同时用color: transparent 来把文字隐藏起来。
1 | document.querySelectorAll('.pie').forEach(function (pie) { |
最终改良版的css代码如下(详情点击此处):
1 | .pie { |
SVG方案
原理:我们先从一个svg开始,画一个半径为50的圆。
1 | <svg width="100" height="100"> |
给圆加点基础样式,描边,如下图1所示。
1 | circle { |
在上面的样式中追加下面一行代码,使描边变成虚线,如下图2所示。
1 | .stroke-dasharray: 20 10; /* 虚线的线段长度为20 且间隙长度为10 */ |
当我们把这个虚线描边的线段长度指定为0 ,并且把虚线间隙的长度设置为等于或大于整个圆周的长度,这里是$2π×30≈ 189$时。我们可以看到,它完全去除了描边效果,只剩下绿色的圆形。当我们开始增加第一个值时,整个圆周上覆盖的长度正是我们给它指定的长度值。
根据这个原理,我们优化一下代码,首先我们为了便于计算,我们把圆的周长设为100,那么半径就是$100/2π≈ 16$,然后我们还需要把svg图形以逆时针方向旋转 90°,使描边起始点位置来到0点钟方向,这样我们就得到了一个60%比率的饼图,代码如下(详情点此处):
1 | <svg viewBox="0 0 32 32"> |
1 | svg { |
利用这个原理,我们也可以实现多色饼图,我们每多一种颜色多添加一个circle层,同时越靠近起点的circle层在图层的越上层。代码如下(详情点此处):
1 | <svg viewBox="0 0 32 32"> |
1 | svg { |
conic-gradient方案
结合以前学的linear-gradient的知识,利用圆锥渐变conic-gradient实现多种颜色的饼图真是太easy了~墙裂推荐!代码如下(详情点此处):
1 | .conic { |
通过三种生成饼图的方案,可以看出:svg方案和conic-gradient方案代码简洁,且能轻松实现多种颜色的饼图,非常值得推荐~