【SVG学习】02-SVG动画
发表于:2024-09-02 |

前言

本篇浅浅聊一下 SVG 动画,它可以让我们的页面效果更加流动。看本篇之前你需要一定的 SVG 知识,如果你没啥其他资料的话可以看看我之前的 SVG 文章。

基于 transform 变化

直播中小动画

这个动画和 CSS 非常的像,比如我们想实现一个直播中的小动画,就可以这样实现

先写一个静态的样式
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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Svg的Transform变化</title>
</head>
<body>
<svg height="0" width="0">
<symbol id="beats" viewBox="0 0 100 100">
<line
class="beat"
x1="15"
y1="40"
x2="15"
y2="100"
stroke="currentColor"
stroke-width="10"
stroke-linecap="round"
></line>
<line
class="beat"
x1="50"
y1="40"
x2="50"
y2="100"
stroke="currentColor"
stroke-width="10"
stroke-linecap="round"
></line>
<line
class="beat"
x1="85"
y1="40"
x2="85"
y2="100"
stroke="currentColor"
stroke-width="10"
stroke-linecap="round"
></line>
</symbol>
</svg>
<span
style="
color: #ff6699;
border: 1px solid #ff6699;
border-radius: 5px;
padding: 5px;
font-size: 24px;
"
>
<svg height="30" width="30">
<use href="#beats"></use>
<!--href="#beats"所指向的就是上面<symbol>标签的id-->
</svg>
<span>直播中</span>
</span>
</body>
<style></style>
</html>

使用<line>标签绘制三条竖直的线条,(x1,y1)是线条起始点的坐标,(x2,y2)是线条终点的坐标。

效果图

然后我们给 svg 内容加上 css 动画样式

这里我们将变化起始点放到 svg 的底部,设置 y 方向的变化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
.beat {
transform-origin: bottom;
animation: beat-scale 1.4s linear infinite;
}

@keyframes beat-scale {
25% {
transform: scaleY(0.3);
}
50% {
transform: scaleY(1);
}
75% {
transform: scaleY(0.3);
}
}

此时三条线是一起运动的,我们加个延时

延时动画
1
2
3
4
5
6
.beat:nth-child(1) {
animation-delay: 0.4s;
}
.beat:nth-child(2) {
animation-delay: 0.2s;
}

此时的效果就很不错了

描边动画

基本概念

描边动画的核心是 SVG 的两个显示属性,分别是 stroke-dasharray 和 stroke-dashoffset。

stroke:定义笔触的颜色。例如:stroke=”green”

stroke-dasharray:

定义 dash 和 gap 的长度。它主要是通过使用 , 来分隔 实线 和 间隔 的值。例如:stroke-dasharray=”5, 5” 表示,按照 实线为 5,间隔为 5 的排布重复下去。如下图:
效果图
stroke-dasharray 并不局限于只能设置两个值,要知道,它本身的含义是设置最小重复单元,即,dash,gap,dash,gap…。比如,我定义 stroke-dasharray=”15, 10, 5” 则相当于,[15,10,5] 为一段。则有:
效果图

stroke-dashoffset*:

用来设置 dasharray 定义其实 dash 线条开始的位置。值可以为 number || percentage。百分数是相对于 SVG 的 viewport。通常结合 dasharray 可以实现线条的运动。相对于绘制的起点偏移的量,正值(向右或者顺时针偏移),负值(向左或者逆时针)

偏移量计算公式:
1
2
d = s - | 'stroke-dashoffset' | mod s
d:偏移量 s:线段总长度 'stroke-dashoffset':参数值

从公式我们可以看出偏移量是一个区间的值,无论参数值多大,偏移量不会大于线段总长度

stroke-linecap:

线条的端点样式。

stroke-linejoin:

线条连接的样式

stroke-miterlimit:

一个比较复杂的概念,如果我们只是画一些一般的线段,使用上面 linejoin 即可。如果涉及对边角要求比较高的,则可以使用该属性进行定义。它的值,其实就是角长度比上线宽:

效果图

而实际理解的话,就是假设当 width 为 1。此时比例为 2。那么 miter = 2。那么超过 2 的 miter 部分则会被 cut 掉。可以参照:
效果图

他主要是配合 linejoin 一起使用。因为 linejoin 默认取值就是 miter。所以,默认情况下就可以使用该标签属性。它默认值为 4。其余的大家下去实践一下即可。

stroke-opacity:

线段的透明度

stroke-width:

线的粗细。

文字霓虹灯

基于上面的知识,我们来做一个简单的文字动画。复杂的动画需要 svg 生成器或者找 UI 去搞,毕竟咱也不是很专业。

初始描边

先初始化一下描边动画

HTML
1
2
3
<svg width="100%" height="100">
<text text-anchor="middle" x="50%" y="50%" class="text">codesigner</text>
</svg>
CSS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
.text {
font-size: 64px;
font-weight: bold;
text-transform: uppercase;
fill: none;
stroke-width: 2px;
stroke-dasharray: 90 310;
animation: stroke 6s infinite linear;
stroke: #3498db;
text-shadow: 0 0 5px #3498db;
}

@keyframes stroke {
100% {
stroke-dashoffset: -400;
}
}

这里我定义了一段为 400–>90+310 90 是实线的,310 是间隔

多组动画完成霓虹灯

然后我们通过多个文字加上动画延迟得到文字霓虹灯的效果

HTML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<svg width="100%" height="100">
<text text-anchor="middle" x="50%" y="50%" class="text text-1">
codesigner
</text>
<text text-anchor="middle" x="50%" y="50%" class="text text-2">
codesigner
</text>
<text text-anchor="middle" x="50%" y="50%" class="text text-3">
codesigner
</text>
<text text-anchor="middle" x="50%" y="50%" class="text text-4">
codesigner
</text>
</svg>
CSS
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
.text {
font-size: 64px;
font-weight: bold;
text-transform: uppercase;
fill: none;
stroke-width: 2px;
stroke-dasharray: 90 310;
animation: stroke 6s infinite linear;
}
.text-1 {
stroke: #3498db;
text-shadow: 0 0 5px #3498db;
animation-delay: -1.5s;
}

.text-2 {
stroke: #f39c12;
text-shadow: 0 0 5px #f39c12;
animation-delay: -3s;
}

.text-3 {
stroke: #e74c3c;
text-shadow: 0 0 5px #e74c3c;
animation-delay: -4.5s;
}

.text-4 {
stroke: #9b59b6;
text-shadow: 0 0 5px #9b59b6;
animation-delay: -6s;
}

@keyframes stroke {
100% {
stroke-dashoffset: -400;
}
}

Icon 描边动画

我们随便去阿里巴巴 icon 找一个 icon 的 svg 复制下来,我复制了一个勾选的 svg

svg 内容

这里需要给 path 加上个 class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<svg
t="1725257031579"
class="icon"
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
p-id="4268"
width="200"
height="200"
>
<path
class="icon-path"
d="M512 0C230.4 0 0 230.4 0 512s230.4 512 512 512c281.6 0 512-230.4 512-512S793.6 0 512 0zM838.4 371.2l-384 384C448 761.6 428.8 768 416 768c-12.8 0-25.6-6.4-38.4-12.8L147.2 524.8C128 505.6 128 467.2 147.2 448c19.2-19.2 57.6-19.2 76.8 0l192 192 345.6-345.6c19.2-19.2 57.6-19.2 76.8 0C864 313.6 864 345.6 838.4 371.2z"
p-id="4269"
fill="#1afa29"
></path>
</svg>
设置 stroke-dasharray 的大小

svg 元素上有一个方法 getTotalLength 方法去获取到当前 path 的长度,是给 stroke-dasharray 赋值
如果这时候有多个 path 元素,就去它们分别计算取最大值。
这里通过 getTotalLength 去取值,并且取最大值的原因是:

如果随便设置一个很大值,这时候如果业务场景需要不断地重复进行描边动画,这时候因为 stroke-dasharray 设置过大,会导致动画停留过久
而设置过小则因为 stroke-dasharray 方法是对图形切割的,会导致图形出现断层

1
2
3
4
5
6
7
<script>
document.addEventListener("DOMContentLoaded", () => {
const logo = document.querySelector(".icon-path");
console.log(logo.getTotalLength());
});
</script>

设置动画
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
.icon {
width: 300px;
height: 300px;
}
.icon-path {
fill: none;
animation: animation 5s linear forwards;
}
@keyframes animation {
0% {
fill: white;
stroke: #333;
stroke-dasharray: 5267.5693359375;
stroke-dashoffset: 5267.5693359375;
}
50% {
fill: white;
stroke: #333;
stroke-dasharray: 5267.5693359375;
stroke-dashoffset: 0;
}
75% {
fill: #e5e7e7;
stroke: white;
}
100% {
fill: #90c9c4;
stroke: white;
}
}

animation 是实现动画的关键,我们来介绍其中的几个比较容易忽略的属性

  1. linear 这代表动画效果,可以参考animation-timing-function
  2. forwards 代表最后一帧是保持住当前图形而不用回到初始的时候。可以参考animation-fill-mode
    这里 animation 还可以设置数字或者 infinite 代表重复次数或者无穷次,不设置则不重复,可以参考animation-iteration-count
  3. 5267.5693359375 是我经过 getTotalLength 去计算出的最大值。
  4. 0% 到 50% 是实现图形从无到有的形成过程, stroke-dashoffset 设置为 0 代表展示图形
  5. 75%到 100%的过程代表为图形上色的过程,这里的 fill 代表填充的颜色,这一般在拿到 svg 元素的就已经写在 svg 标签里了

在理解了这个之后,那么我们平时使用的阿里巴巴 icon 的 svg 都可以进行动画加载

多个 path 的 Icon 描边动画

接下来我们进阶一下,进行多个 path 的描边动画。

找个图测试一下

我们先去 iconfont 找一个插画矢量图,比如我找的就是这个
效果图

找到 svg 中关于文字的部分

打开浏览器页面调试器或者 UI 工具(这个我不多介绍了,太多了),我拿一个最简单的浏览器调试器来说,你就一一对应去找到你想做动画的部分
效果图
然后给那些 path 加上icon-path的 class,这个你喜欢加啥就加啥,你能对应起来就行

实现动画

这里先得到了maxPath,如果你确定文字大小路径差不多的话也可以不加这个循环。

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
function setStyle(target, styles) {
for (const k in styles) {
target.style[k] = styles[k];
}
}
const pathElements = document.querySelectorAll(".icon-path");
document.addEventListener("DOMContentLoaded", () => {
let maxPath = 0;
for (let i = 0; i < pathElements.length; i++) {
const pathElement = pathElements[i];
maxPath = Math.max(maxPath, pathElement.getTotalLength());
}
for (let i = 0; i < pathElements.length; i++) {
const pathElement = pathElements[i];
const fill = pathElement.getAttribute("fill");
const path = {
fill: "none",
animation: `animation${i} 5s linear 1 forwards`,
};
setStyle(pathElement, path);
document.styleSheets[0].insertRule(
`
@keyframes animation${i} {
0% {
fill: white;
stroke: #0c0426;
stroke-dasharray: ${maxPath};
stroke-dashoffset: ${maxPath};
}
50% {
fill: white;
stroke: #0c0426;
stroke-dasharray: ${maxPath};
stroke-dashoffset: 0;
}
100% {
fill: ${fill};
stroke: ${fill};
}
}
`
);
}
});

其他动画

当然,svg内置了animate的标签,我们可以使用animate来做一些简单形变动画等,这里我不展开了,大家感兴趣的话可以自己去看一下文档

结语

本文主要就是给大家分享一下,我们的svg其实可以用来实现动画,阿里巴巴icon等iconfont库并不是我们简简单单当图片用就发挥了它的能力,我们完全可以利用svg来制作动画,让我们的页面更加流动,本篇文章就到这里了,更多内容敬请期待,债见。

上一篇:
【可视化学习】83-从入门到放弃WebGL(十五)
下一篇:
【可视化学习】82-从入门到放弃WebGL(十四)