SVG做个loading

前端做动画有哪些方式?各有什么优缺点?

css3

  • transition
  • animation

利用css3上述两种动画可以满足我们都日常需求,但是有很多效果它做不到,比如下面我们要说都一个loading

js + css

通过js去动态去改变css属性也可以实现动画,只是在上面css3动画都基础上我们还要去操作dom

canvas + js

利用canvas画图更新每一帧做动画,很多h5游戏也是这么做的,它更适合游戏这样复杂等场景

gif

利用photoshop等工具制作gif,在项目中直接引用。在需求特别紧急,而且动画又特别复杂的情况下,自己没有把握按时实现效果,或者代价太大,真的,别犹豫,上gif图片。

gif的不足就是修改它就得去换图,并且质量差,容易虚

svg

都知道svg是矢量图,放大不失真,却不是谁都知道svg可以做动画,比如最近跟我合作的设计师….

svg可以用它的animate标签来做动画,也可以用css3做动画。

用svg做动画我们可以随意缩放,并且无需操作dom。

动手做一个svg动画

今天主要要说的也就是怎么利用svg做动画,下面我们会做这样一个loading

咱们需要的loading要求为:在父容器为29*29时,直径(含边框)为29,边框宽为4,边分为深灰和浅灰两段,
这两段分别执行长短变化的动画,整体有旋转动画。深灰色部分长度
改变父容器宽高,loading能等比缩放。

先介绍一种简单的方法,如果你的交互设计师使用AE(Adobe After Effects)设计的动画效果,那么你可以推荐它使用bodymovin这款插件帮你直接倒出svg动画,
可以直接导出demo,但是这个svg是要依赖bodymovin.js库去播放的🐶🐶,可参考 大杀器Bodymovin和Lottie:把AE动画转换成HTML5/Android/iOS原生动画

言归正传:怎么实现上面这个动画?

首先了解一下原理

1
2
3
4
5
6
<svg class="loading" width="100%" height="100%" viewBox="0 0 29 29">
<circle class="c1" cx="14.5" cy="14.5" r="12.5" fill="none" stroke-width="4"
stroke="#b1b1b1" />
<circle class="c2" cx="14.5" cy="14.5" r="12.5" fill="none" stroke-width="4"
stroke="#c7c7c7" />
</svg>

首先认识一下上面svg中的基本属性:
cicle用来画一个圆,fill="none"将圆掏空为边框,stroke-width为边框宽度,cxcy属性定义圆点的 x 和 y 坐标,r为圆的半径

  • viewBox
    viewBox的四个参数分别代表:最小X轴数值;最小y轴数值;宽度;高度。
    前两个暂时用不到,可理解为除对内部svg做整体位移,一般都是0 0,暂时先不做解释,重点关注后两个参数,可理解为svg的画布大小,会将内部按照在此宽高基础上的占比,放大至svg的宽高。
    不理解的话可以看 理解SVG viewport,viewBox,preserveAspectRatio缩放/SVG之ViewBox

    为了使svg内部能撑满,这里设置viewBox="0 0 29 29"

  • stroke-dasharray
    重点来了,stroke-dasharray在SVG中表示描边是虚线,两个值,第一个是虚线的宽度,第二个是虚线之间的间距。有点像css中dashed的border。我们就是利用它也画我们弧形的边框,以及后续控制边框长短。

理解动画过程

先画出圆,这一步相对简单,主要是使用合适的viewBox。另外svg的css记display:block,否则在容器较小的会有莫名的边距的。

1
2
3
4
5
6
<svg class="loading" width="100%" height="100%" viewBox="0 0 29 29">
<circle class="c1" cx="14.5" cy="14.5" r="12.5" fill="none" stroke-width="4"
stroke="#b1b1b1" />
<circle class="c2" cx="14.5" cy="14.5" r="12.5" fill="none" stroke-width="4"
stroke="#c7c7c7" />
</svg>

深灰色圆弧c1的长度变化动画

c1的动画相对简单,stroke-dashoffset默认为0即可,主要是利用stroke-dasharray控制圆环弧度的长。

通过的两个参数的意义,我们可以推测,c1的stroke-dasharray第一个参数为1,第二个参数为周长2πr的值时刚好只剩一个小边,第一个参数为周长,第二个参数也为周长
时刚好成为360度的圆环。尝试发现确实是。

我们假定stroke-dasharray:a b即第一个参数为a,第二个参数为b,则b为周长,a从1向上加即可得到顺时针的c1的长度动画。如果想要按照需要的长度区间,则可以根据周长的占比计算。
比如:长度在1.832s内从78%到31%再到78%的动画,动画曲线cubic-bezier(0.18, 0, 0.58, 1)。

1
2
3
4
5
6
7
8
9
10
11
.c1{
animation: long2 1.832s cubic-bezier(0.18, 0, 0.58, 1) 0s infinite;
}
@keyframes long2 {
0% {
stroke-dasharray: 60.6 78;
}
50% {
stroke-dasharray: 23.9 78; }
100% {
stroke-dasharray: 60.6 78; } }

浅灰色圆弧c2的长度变化动画

假定c2的stroke-dasharray:a b即第一个参数为a,a的长度由占比周长得到,第二个参数为b,从上面知道b的值一样需要是圆的周长。storke-dashoffset: c参数为c。
由于c1在动画过程中长度会变,这个过程c1的起点不变,尾巴在顺时针变长变短。而c2需要紧跟c1,那么我们就需要让c2往反方向么也就是逆时针做长度变化的动画,控制c2的storke-dashoffset值让它紧跟c1,
当c == 2
a的时候,c2刚好与c1衔接。(c2的stroke-dashoffset再减一个1 免得连不上)
比如:长度在1.832s内从12%到4%再到12%的动画,动画曲线cubic-bezier(0.18, 0, 0.58, 1)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
.c2{
animation: short 1.832s cubic-bezier(0.18, 0, 0.58, 1) 0s infinite;
}
@keyframes short2 {
0% {
stroke-dasharray: 9.36 78;
/*12*/
stroke-dashoffset: 17.72;
/* 9.36*2 - 1 */ }
50% {
stroke-dasharray: 5.46 78;
/*4*/
stroke-dashoffset: 9.92; }
100% {
stroke-dasharray: 9.36 78;
/*12*/
stroke-dashoffset: 17.72; } }

旋转动画

旋转动画有多种实现方式

  • css3 animate动画
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    .loading {
    animation: rotate 1.832s linear 0s infinite;
    display: block;//一定记得svg转块,不然小的时候莫名的被父元素挤出来
    }
    @keyframes rotate {
    0% {
    transform: rotate(0deg);
    }
    50% {
    transform: rotate(360deg);
    }
    100% {
    transform: rotate(720deg);
    }
    }
  • svg内部animate
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    <svg class="full-loading" viewBox="0 0 29 29">
    <circle class="c1">
    <animateTransform
    attributeName="transform"
    type="rotate"
    begin="0s"
    dur="1.832s"
    from="0 14.5 14.5"
    to="720 14.5 14.5"
    repeatCount="indefinite" />
    </circle>
    <circle class="c2">
    <animateTransform
    attributeName="transform"
    type="rotate"
    begin="0s"
    dur="1.832s"
    from="0 14.5 14.5"
    to="720 14.5 14.5"
    repeatCount="indefinite" />
    </circle>
    </svg>

这里要注意,下面这样不行:

1
2
3
4
5
6
7
8
9
10
11
12
<svg class="full-loading" viewBox="0 0 29 29">
<animateTransform
attributeName="transform"
type="rotate"
begin="0s"
dur="1.832s"
from="0 14.5 14.5"
to="720 14.5 14.5"
repeatCount="indefinite" />
<circle class="c1"/>
<circle class="c2"/>
</svg>

至此,loading已完成,结果可以看最上面。

做成一个svg文件

上面我们虽然完成了svg动画,但是是分为css和svg两部分,能不能合成一个svg文件呢?
我们这么把css嵌入svg文件呢?
详细内容你可以看看SVG element reference

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
<?xml version="1.0"?>
<svg width="200" height="200" viewPort="0 0 200 300" version="1.1" xmlns="http://www.w3.org/2000/svg">

<svg class="full-loading" viewBox="0 0 29 29">
<circle class="c1">
<animateTransform
attributeName="transform"
type="rotate"
begin="0s"
dur="1.832s"
from="0 14.5 14.5"
to="720 14.5 14.5"
repeatCount="indefinite"/>
</circle>
<circle class="c2">
<animateTransform
attributeName="transform"
type="rotate"
begin="0s"
dur="1.832s"
from="0 14.5 14.5"
to="720 14.5 14.5"
repeatCount="indefinite"/>
</circle>
</svg>
<style><![CDATA[

.full-loading {
-webkit-animation: rotate 1.832s linear 0s infinite;
animation: rotate 1.832s linear 0s infinite;
display: block; }

circle.c1 {
stroke: #929292;
-webkit-animation: long 1.832s cubic-bezier(0.18, 0, 0.58, 1) 0s infinite;
animation: long 1.832s cubic-bezier(0.18, 0, 0.58, 1) 0s infinite; }
.night circle.c1 {
stroke: #606060; }

circle.c2 {
stroke: #B1B1B1;
-webkit-animation: short 1.832s cubic-bezier(0.18, 0, 0.58, 1) 0s infinite;
animation: short 1.832s cubic-bezier(0.18, 0, 0.58, 1) 0s infinite; }
.night circle.c2 {
stroke: #4E4E4E; }
.full-loading {
width: 100%;
height: 100%; }
.full-loading circle {
cx: 14.5;
cy: 14.5;
r: 12.5;
fill: none;
stroke-width: 4; }
.full-loading circle.c2 {
stroke-dasharray: 9.36 78;
/*12*/
stroke-dashoffset: 17.72;
-webkit-animation-name: short2;
animation-name: short2; }
.full-loading circle.c1 {
stroke-dasharray: 60.6 78;
/*78*/
stroke-dashoffset: 0;
-webkit-animation-name: long2;
animation-name: long2; }

@-webkit-keyframes long2 {
0% {
stroke-dasharray: 60.6 78;
/*78*/ }
50% {
stroke-dasharray: 23.9 78;
/*31*/}
100% {
stroke-dasharray: 60.6 78; } }

@keyframes long2 {
0% {
stroke-dasharray: 60.6 78;
/*78*/ }
50% {
stroke-dasharray: 23.9 78; }
100% {
stroke-dasharray: 60.6 78; } }

@-webkit-keyframes short2 {
0% {
stroke-dasharray: 9.36 78;
/*12*/
stroke-dashoffset: 17.72;
/* 9.36*2 - 1 */ }
50% {
stroke-dasharray: 5.46 78;
/*4*/
stroke-dashoffset: 9.92; }
100% {
stroke-dasharray: 9.36 78;
/*12*/
stroke-dashoffset: 17.72; } }

@keyframes short2 {
0% {
stroke-dasharray: 9.36 78;
/*12*/
stroke-dashoffset: 17.72;
/* 9.36*2 - 1 */ }
50% {
stroke-dasharray: 5.46 78;
/*4*/
stroke-dashoffset: 9.92; }
100% {
stroke-dasharray: 9.36 78;
/*12*/
stroke-dashoffset: 17.72; } }
]]></style>
</svg>

这里的旋转用的animate标签,因为animate的rotate动画拿进来不行,很诡异。