用 SVG 做个动画

现代网页前端开发实现动画效果相比以往的Flash、GIF越发丰富和成熟,其中就包括了CSS3、Canvas、SVG。利用CSS3可以使DOM元素根据设置好的动画过程动起来,Canvas即可以通过JavaScript在画布上绘画出各种图案。今天要说SVG则有别于两者,SVG最初是XML家族作为一种通用的数据格式,后逐渐发展到今天成为一种图像的数据甚至有着不断发展成为Every Things的潜力。

SVG

SVG 全称 Scalable Vector Graphics,可缩放矢量图形。是一种用XML语言描述的二位矢量图形格式。由 W3C 制定,是一个开放的标准。更多的可以看 wiki

诞生

1996年 Chris Lilley 起草了一份Web的矢量图形需求,到1998年就已经有六个方案提交到 W3C 中:

  • Web Schematics, 由 CCLRC(Science and Technology Facilities Council) 提交。
  • PGML, 由 Adobe, IBM, Netscape, 和 Sun 提交
  • VML, 由 Autodesk, Hewlett-Packard, Macromedia, 和 Microsoft 提交
  • Hyper Graphics Markup Language, 由 Orange, PCSL, 和 PRP 提交
  • WebCGM, 由 Boeing, CCLRC, Inso, JISC, 和 Xerox 提交
  • DrawML, 由 Excosoft 提交

当时 W3C 并不想 Web 上的矢量图片被这些企业巨头所垄断,随后就成立了一个专门小组 SVG Working Group,以借鉴 PGML 和 VML 两种提案后,提出了 SVG 规范。

小插曲:Adobe说 PGML 仅仅只是一份提议,很高兴去修改它。不过 Microsoft 说 VML 已经应用到商业产品上的文档,W3C 无权去进行任何更改

发展

2011年8月16日,W3C 发布了 SVG 1.1(第2版),也是 W3C 目前推荐的标准。2017年的今天,几乎所有现代浏览器已经实现了对 SVG 基础的支持,全球浏览器支持率达 97.88% ,中国为 93.34%。

  • IE 9.0 及以上。
  • Firefox 、Chrome 几乎全部支持。
  • Android 4.4 及以上。
  • IOS 几乎全部支持。

SVG 相比一般常用的jpeg、png、gif图像格式,它具有可读性(XML)、可嵌入脚本、时序控制和动画、方便创建文字索引等特性。在这些特性的支持下,作为 Web 前端开发者就能利用 SVG 创作更多的可能性,本文将会一一介绍和通过简单的例子来展现出 SVG 的强大。

通过上面的描述来看,从今天开始学习并且使用 SVG 都能得到良好的支持。更何况 Adobe 的AI、PS已经支持导出 SVG 的情况下,能更好的配合设计妹子的工作。

学习

通过上面对 SVG 的了解,我们可以清晰的知道它是一种以XML为基础的“结构化数据”,相信对于前端开发者们驾轻就熟了。SVG 涉及的元素就多达94个(来自SVG element reference所列出的),元素分类就足足有19项,其中包括了一些动画元素、基础图形、容器元素、描述元素、过滤原始元素……但从这些数字来看就证明 SVG 的功能足够的丰富。

如何开始

在现代浏览器井喷式发展的背后,各个浏览器对 SVG 的支持也在不断跟进发展,其中已知使用的方法就包含了:

  • 使用 <img> 标签,通过 src 属性载入一个 *.svg 文件,将 SVG 与传统的互联网图片格式同等使用。(常见)
  • 使用 <svg> 标签,在 html 文件上进行定义。(常见)
  • 使用 <embed> 标签,Adobe 公司为不原生支持 SVG 的浏览器开发的插件 Adobe SVG Viewer。(2009年1月1日 Adobe 已终止支持)
  • 使用 <object> 标签,通过 data 属性载入一个 *.svg 文件。
  • 使用 <iframe> 标签,通过 src 属性载入一个 *.svg 文件。

但按照目前的浏览器发展进程,前两种方法已经得到很好的支持,所以推荐大家只需使用前两种即可。

简单的例子

我们首先学习最简单的例子,如何画出一个红色的“X”。这是相当基础的一个绘图,我们需要定义一个画布其中包括画布的宽高,在画布上根据各自坐标系画出两条交叉的线。

1
2
3
4
5
6
7
8
<svg>
<!-- 第一条线从 (20,100) 开始画一条直线到 (100,20) -->
<line x1="20" y1="100" x2="100" y2="20"
stroke-width="2" stroke="red"/>
<!-- 第二条线 (100,100) 开始画一条直线到 (20,20) -->
<line x1="100" y1="100" x2="20" y2="20"
stroke-width="2" stroke="red"/>
</svg>

实现动画

SVG 在最初提案的时候就加入了对动画实现的考虑,SVG 的动画是通过 W3C 颁布的另一种动画语言 SIML(Synchronized Multimedia Integration Language)实现。

SIML 与 SVG 一样,都是一种声明性的标记语言,通过元素和元素的属性来定义动画的行为。所以在使用 SVG 实现动画过程中 SIML 语言的元素也作为 SVG 的元素一样定义在 <svg> 标签内。

animate

<animate>,通过改变父元素的属性的值达到动画的效果。

1
2
3
4
5
6
7
8
<svg width="120" height="120" viewPort="0 0 120 120" version="1.1"
xmlns="http://www.w3.org/2000/svg">·

<rect x="10" y="10" width="100" height="100">
<animate attributeType="XML" attributeName="x" from="-100" to="120"
dur="10s" repeatCount="indefinite"/>
</rect>
</svg>

【解读代码】

  • <rect>添加子动画元素<animate>
  • 设置改变的属性(attributeName)为”x”。
  • 设置“x”的值从(from) -100 到(to) 120 进行改变。
  • 动画时长(dur)10s。
  • 重复次数(repeatCount)无限重复。

<animateColor> 元素会在未来的标准中删除,如果需要修改颜色可以利用<animate>实现

animateMotion

<animateMotion>根据路径(<path>)进行位移运动。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<svg width="120" height="120" viewBox="0 0 120 120">
<!--运动路径-->
<path d="M10,110 A120,120 -45 0,1 110 10 A120,120 -45 0,1 10,110"
stroke="lightgrey" stroke-width="2"
fill="none" id="theMotionPath"/>
<!--运动的红点-->
<circle cx="" cy="" r="5" fill="red">
<!--定义动画元素-->
<animateMotion dur="6s" repeatCount="indefinite">
<!--链接路径-->
<mpath xlink:href="#theMotionPath"/>
</animateMotion>
</circle>
</svg>

【解读代码】

  • 使用<path>元素定义运动路径。
  • 定义<animateMotion>元素,然后利用<mpath>引入运动的目标路径。
  • 动画时长(dur)6s。
  • 重复次数(repeatCount)无限重复。

由于<path>涵盖的内容较多,说起来会导致篇幅过大所以在这里不进行详述,日后新开一篇文章进行详述。如果有需要可以阅读GcsSloop的文章

animateTransform

<animateTransform>改变父元素的变形属性,从而允许动画控制转换、缩放、旋转或斜切。

1
2
3
4
5
6
7
8
9
10
11
12
13
<svg width="120" height="120"  viewBox="0 0 120 120" >
<!--多边形-->
<polygon points="60,30 90,90 30,90">
<!--定义表行属性动画-->
<animateTransform attributeName="transform"
attributeType="XML"
type="rotate"
from="0 60 70"
to="360 60 70"
dur="10s"
repeatCount="indefinite"/>
</polygon>
</svg>

【解读代码】

  • 定义旋转(type="rotate")的动画,其他值:”translate”, “scale”, “skewX” 或 “skewY”。
  • 其中 fromto 有三个值 n1 n2 n3
  • 旋转动画从 0度 到 360度。 (n1的值)
  • 旋转的圆心坐标为60,70。 (n2,n3 的值)。
  • 动画时长(dur)4s。
  • 重复次数(repeatCount)无限重复。

discard

当添加子元素 <discard> 的时候,该元素会在指定时间内被消除,从而帮助浏览器释放更多的内存,对于较大的多个svg元素组成的动画有一定帮助。

1
2
3
4
5
6
7
<svg width="120" height="120"  viewBox="0 0 120 120" >
<!--多边形-->
<polygon points="60,30 90,90 30,90">
<!--将在1.2s后被消除-->
<discard begin="1.2s"/>
</polygon>
</svg>

利用脚本处理动画

上面说到了 SVG 可以利用本身的元素进行一些相对简单的动画效果,但是这种实现方式需要和用户进行交互的时候就显得不好控制了,所以在 SVG 最初的设计过程中就考虑到了可编程性的需求,作为被用于互联网的一个技术,所以通过 JavaScript 和 DOM 访问它就是成了最重要的应用模式。

下面将会结合 JavaScript 和大家学习一下 SVG 的可编程性。

进度条

进度条是我们在应用开发过程中最容易接触到的元素,以往我们在编写进度条的样式往往是通过修改元素的宽带来达到进度条的效果。但想要酷炫的进度条我们就要使用 SVG 了。其实在 HTML 里直接编写和使用 SVG 过程中我们都可以通过 JavaScript 直接反问或者修改元素的值。

下面我们就用一个进度条来演示一下,JavaScript 如何动态修改 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
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
<html>

<head>
<title> 进度条 </title>
<script language='JavaScript'>
function ProgressBar(info) {
var stem = {}; // 此函数最后返回的代表进度条的对象。
var done = 0, length, outline, bar; // 声明内部变量。

bar = document.getElementById("done"); // 进度条中绿色的变化部分。
length = 80;

// 重置进度到零。
function reset() {
return to(0);
}
// 设置进度到某个值。
function to(value) {
if (value >= 100) {
done = 100;
bar.setAttribute("width", length);
} else {
done = value;
bar.setAttribute("width", Math.round(done * length / 100));
}
return stem;
}
// 进度变化某个值。
function advance(step) {
return to(done + step);
}
// 以下给进度条对象添加方法。
// 获得当前进度值。
stem.getDone = function() {
return done;
};
stem.reset = reset;
stem.to = to;
stem.advance = advance;
return stem; // 返回可供脚本使用的进度条对象。
}
// 测试进度条对象。
function testBar() {
var bar = ProgressBar();
// 此内部函数每运行一次,增加进度值 1,直到进度值为 100。
function test() {
if (bar.getDone() === 100) {
clearInterval(id);
} else {
bar.advance(1);
}
}
// 每十分之一秒改变一次进度。
var id = setInterval(test, 100);
}
// 页面载入后开始测试。
window.addEventListener("load", testBar, true);
</script>
</head>

<body>
<div id='svgDiv'>
<svg viewBox="0 0 100 100" style="border:1px solid; width:200px; height:200px; ">
<!--定义一个进度条组(Group)-->
<g id='progBar'>
<!--进度条的外框,灰色边框白色背景。-->
<rect x='10' y='40' width='80' height='20' stroke='grey' fill='white'/>
<!--进度条绿色部分-->
<rect id='done' x='10' y='40' width='0' height='20' fill='green'/>
</g>
</svg>
</div>
</body>

</html>

【解读代码】

  • 先定义 HTML 元素。
  • 定义一个 SVG 元素,并设置 viewBox 为 0,0,100,100。(viewBox 的设置也牵扯到很多学问,日后再开一篇文章说说。)
  • 定义一个容器元素 <g> 用于分组,id 为 “progBar” 。
  • 在 progBar 容器中添加两个矩形元素。第一个作为外层边框,第二个作为绿色的层显示进度并定义。
  • 在 JavaScript 脚本中我们用 DOM 获得绿色矩形并且进行属性修改。

SVG 可以结合脚本代码使用最大的好处就是可以用户产生交互, JavaScript 对 SVG 的操作和平常对 DOM 的操作并无太大不同,getElementById 和 setAttribute 的用法和在 HTML 中没有两样。而且这些脚本代码也可以存放在 .svg 文件中,只不过在 .svg 文件中 script 标签后需要在两侧加上 CDATA 标注。如:

1
2
3
4
5
 <script language='JavaScript'>
/* <![CDATA[ */
....
/* ]]> */
</script>

性能的比较

SVG 和 Canvas 的性能对比上,其实都有优劣。在屏幕越大的情况下 Canvas 因为需要绘制的像素更多,所以性能越低。但在对象越多的情况下,SVG 就显得力不从心因为过多的对象也是会加入到 DOM 中的。如果想要了解更多,可以看 Canvas 与 SVG 的对比。

SVG 和 GIF 图片的对比也是部分读者关心的,毕竟有时候我们一些状态图标就在使用 gif 格式的图片。GIF 和 SVG 最大的区别就是,SVG 是矢量图可以在无限放大的情况下仍然保持清晰,而 GIF 就没办法做到了。在对比加载过程中 GIF 图片往往很大而且无法通过 gzip 压缩,而 SVG 由于是属于 XML 的结构文档可以被 gzip 进行压缩,所以相对 GIF 图片来说 SVG 加载速度更快。如果想要了解更多,可以看 Animated SVG vs GIF

总结

SVG 的好处在于方便的可编辑、可通过 JavaScript 控制等,而且随着浏览器的快速发展越来越多的浏览器对其支持并且逐渐完善更多的标准。而且 Adobe 的一系列软件可以将动画导出称为一个 SVG 格式的文件。在如此之多的优势下,我们还有什么理由不使用 SVG 呢?