SVG动画实践

0xinhua 发布于

SVG算不上是一种新技术,早在2001年的时候,已经出台了相应的规范,它是一种使用XML描述2D图形的语言,利用SVG可以做非常多炫酷的动画,结合HTML5、CSS3,SVG就变得更加强大;本篇是SVG实践总结,主要包含以下方面的内容:

  1. 关于SVG
  2. SVG的视窗和坐标体系
  3. SVG 实践
  4. SVG 动画
  5. 附录

关于SVG

SVG是"Scalable Vector Graphics"的简称,中文翻译成“可缩放矢量图形”,从字面意义上有两层意思:可缩放+矢量图,SVG是一种新的描述图像的方式,在这之前,我们用的较多的是以*.jpg、.gif、.png等后缀的图像,这类图像统称为位图,使用一个个像素点来描述图像,所以也叫点阵图;位图的缩放会出现失真的情况,当放大到一定程度会呈现出类似于马赛克的状态,而矢量图是使用点和线来描绘图形,缩放对矢量图的清晰度没有影响;类似于 HTML,SVG 也是使用元素、属性、和样式来构建文档,也存在兼容的问题,浏览器支持情况如下图所示,SVG提供了很全的元素来供我们使用,例如<circle><ellipse><polyline><path>....依次代表创建圆、椭圆、曲线、路径等,其中path是最强大的一个标签,没有之一,文章的后面会再次提到。 SVG SVG 的优势:

  1. SVG是可伸缩的,并且不依赖分辨率;
  2. 与JPEG和GIF图像比起来,体积更小;
  3. 纯粹的XML;
  4. 多种方式嵌入到网页中;
  5. 用于查看和打印高保真文档;

SVG的坐标体系

在进行SVG开发之前,由于不熟悉SVG的viewport、viewBox、坐标系等概念,饶了一些弯路,先熟悉一下viewBox视窗、坐标这几个概念:

viewport

浏览器也有一个viewport,SVG的viewport与之类似,不同的是SVG这个窗口是可以修改的,“视窗”定义了我们以多大的区域来绘制SVG,这个概念类似于Canvas的画布,可以通过width和height来定义viewport,例如:

html
1<!-- the viewport 大小为 500px 500px --> 2<svg width="500" height="500"> 3 <!-- SVG content --> 4</svg>

视窗可以指定具体的单位,如果没有给定,默认使用“px”单位,支持以下单位:px, %, pc, pt, mm, cm, in, ex, em, 不支持rem、vh、vw等单位。

坐标系

SVG的坐标系和标准的笛卡尔直角坐标系还有点区别,svg坐标系的原点(0,0)位于左上角XY轴交叉点,X轴向右为正方向,向下为Y轴的正方向。

viewBox

viewBox顾名思意“视区盒子”,viewBox和viewport有点让人傻傻分不清楚,其实viewBox是在viewport外存在的另一个坐标体系,用来辅助定义SVG的可视范围,我的理解是类似于使用截屏软件时,viewBox即截图框区域大小,我们可以移动这个框来选择截取位置,既能截全屏,也可以截取特定区域;当没有定义viewBox时,viewBox默认为viewport的大小,viewBox定义四个坐标,分别是:x y width height,x:左上角横坐标,y:左上角纵坐标,width:宽度,height:高度,通过下面的代码来查看viewBox起的作用:

html
1<!-- the viewport 大小为 300px 300px --> 2<svg class="circle-chart" width="300" height="300" xmlns="http://www.w3.org/2000/svg"> 3 <circle class="pie" stroke="#4285f4" stroke-width="20" stroke-dasharray="400,0" stroke-linecap="round" fill="none" cx="0" cy="0" r="63.66197723675813" /> 4</svg>

viewport的宽高均为300,定义一个圆心坐标为(0.0),周长为400的圆,那么圆半径 r = 400 / 2 * Math.PI = 63.66197723675813,圆的边宽度为30,即图中蓝色部分,在没有设置viewBox值的情况下,SVG的视区大小默认为viewport大小,由于坐标原点在左上角,所以我们只看到了圆环的右下1 / 4部分(图中#1所示),如果想看到整个圆环,那么需要将viewBox视角往左上角方向移动,移动的距离为: r + stroke-width / 2 = 73.66197723675813的距离,如代码例子#2所示:

viewBox="-73.66197723675813 -73.66197723675813 300 300"

但这并不是我想要的,我需要将圆环放置在视图的中心位置,那么viewBox的x、y坐标均为-150即可(如代码例子#3所示)

viewBox="-150 -150 300 300"

PreserveAspectRatio属性

如果viewport和viewBox的宽高比不相同,你需要自己来指定如何在SVG阅读器(如浏览器)中显示SVG图像,你可以在SVG中使用preserveAspectRatio属性来指定,preserveAspetRation属性指出了如何缩放及如果对齐viewBox到viewport上,defer参数是可选值,它仅仅在image元素上应用preserveAspectRatio属性时才使用。preserveAspectRatio的align参数是否强制进行均匀的缩放,如果align设置为none,图形会被缩放以适应viewport大小,而不会管它的宽高比。可以将它想象为CSS中的background-position属性,viewBox就好像是背景图像,使用不同的align值就好比在viewport中使用不同的background-position值来定位viewBox一样,

preserveAspectRatio = defer? align meetOrSlice

align有以下9种取值:
xMinYMin:viewBox的对齐viewport的最小X值,min-y对齐viewport的最小Y值。
xMinYMid:viewBox的对齐viewport的最小X值,viewBox的Y轴中点对齐viewport的Y轴中点。
xMinYMax:viewBox的对齐viewport的最小X值,min-y+对齐viewport的最大Y值。
xMidYMin:viewBox的X轴中点对齐viewport的X轴中点,min-y对齐viewport的最小Y值。
xMidYMid(默认值):viewBox的X轴中点对齐viewport的X轴中点,viewBox的Y轴中点对齐viewport的Y轴中点。
xMidYMax:viewBox的X轴中点对齐viewport的X轴中点,min-y+对齐viewport的最大Y值。
xMaxYMin:viewBox的+对齐viewportX轴的最大值,min-y对齐viewport的最小Y值。
xMaxYMid:viewBox的+对齐viewportX轴的最大值,viewBox的Y轴中点对齐viewport的Y轴中点。
xMaxYMax:viewBox的+对齐viewportX轴的最大值,min-y+对齐viewport的最大Y值。

SVG实践

利用SVG实现一个环形图表

给定数据,利用SVG实现一个类似甜甜圈🍩的按百分比划分的环形图,如下图所示:

JavaScript
1let data = [ 2 {value:335, name:'直接访问'}, // pie1 3 {value:310, name:'邮件营销'}, // pie2 4 {value:234, name:'联盟广告'}, // pie3 5 {value:135, name:'视频广告'}, // pie4 6 {value:1548, name:'搜索引擎'} // pie5 7 ]

donut

两种思路:

  1. 利用描边和偏移stroke-dashoffset来完成(下面#4中例子);
  2. 按比例确定扇区的起始位置,利用path绘制各饼图,中间部分用另一个圆形遮盖 ;

按第一种思路,利用SVG神奇的stroke属性来帮我们完成绘图,stroke包含以下几个属性:
stroke-width: 定义一条线,文本或元素轮廓厚度
stroke-linecap: 描边端点表现形式 butt、round、square
stroke-dasharray: 用于创建虚线
stroke-dashoffset: 偏移位置
利用dasharray画出第一个扇区,描边的长度等于它弧长,第二个扇区偏移至第一个扇区结束位置,理一下描边思路,伪代码如下:

js
1 // 伪代码 2 // 总数 3 lat total = 335 + 310 + 234 + 135 + 1548 ; // 2562 4 // 周长 5 let circumference = 400; 6 // pie1扇区的弧长 7 dash1 = 335 / total * circumference; // 52.30288836846214 8 offset1 = 0; 9 // pie2弧长算法同pie1,第二个扇区的偏移dashoffset 10 offset2 = 周长 - 之前扇区的周长 + 第一个扇区的偏移量 11 ~ 以此类推求出每个扇区的dasharray值 和 dashoffset值
CSS
1// 虚线值和偏移量值 2.chart5 circle.pie1 { 3 stroke-dasharray: 52.30288836846214 347.69711163153784; 4 stroke-dashoffset: 0; 5} 6.chart5 circle.pie2 { 7 stroke-dasharray: 48.39968774395004 351.60031225604996; 8 stroke-dashoffset: 347.69711163153784; 9} 10.chart5 circle.pie3 { 11 stroke-dasharray: 36.53395784543326 363.46604215456676; 12 stroke-dashoffset: 299.2974238875878; 13} 14 15.chart5 circle.pie4 { 16 stroke-dasharray: 21.07728337236534 378.92271662763466; 17 stroke-dashoffset: 262.76346604215456; 18} 19 20.chart5 circle.pie5 { 21 stroke-dasharray: 241.68618266978922 158.31381733021078; 22 stroke-dashoffset: 241.68618266978922; 23}

环形图结果如下图#6所示:

代码地址: https://codepen.io/amnEs1a/pen/wpNWWq/

SVG 动画

上面#4的例子就是使用css3的Animation来实现的,因为SVG类似于 HTML,所以CSS3的三大利器(Transitions, Transforms和Animation)同样适用于SVG;SVG的动画还可以通过定义animation elements标签来实现(#5例子),这些标签最初是在SMIL动画规范中定义的,CSS能做的SMIL都能做,如果你偏向于使用JavaScript,可以试试snapsvg

the Snap.svg JavaScript library makes working with your SVG assets as easy as jQuery makes working with the DOM.

snapsvg被定义为SVG界的jQuery,让我们更方便得定义SVG动画。JavaScript实现动画的缺点是当SVG嵌入到img标签或者作为背景图片放入background-image属性中时,动画不起作用,但是例如SVG的path标签,通过定义d属性值来定义path的形状,这部分又无法通过CSS来实现,所以SIML可以说是弥补以上两者的一些缺陷,这里介绍一下使用SVG的SMIL; SMIL允许:

  1. 变动一个元素的数字属性(x、y……)
  2. 变动变形属性(translation或rotation)
  3. 将颜色属性作为动画
  4. 按照运动轨迹移动

As of Chrome 45.0, SMIL animations are deprecated in favor of CSS animations and Web animations .

来看下浏览器对SMIL的支持情况,值得注意的是自Chrome 45.0起,SMIL动画就被废弃了,并且会在console中给出警告提示,推荐使用CSS动画和Web动画。 前面三个CSS3基本都能实现,厉害的是第4个,来体验一下SVG实现动画的几种方式:

animate属性

通过添加SVG动画元素,比如到SVG元素内部来实现动画,对 元素来说,重要的属性有:
attributeName:变动的属性的属性名。
attributeType:属性类型 from:变动的初始值。
to:变动的终值
dur:动画的持续时间
fill: 是否保留动画结果 repeatCount: 重复次数indefinite表示无限重复

会发现类似于CSS3 animate的定义方式,如何把我们写的animate标签作用于SVG上呢?SMIL提供了两种方式:

  1. 将animate标签放包裹在SVG标签中:

    HTML
    1<circle id="chart5" ... > 2 <animate ... /> 3 <!-- animate动画标签 --> 4</rect>
  2. 使用xlink:href属性,指定作用于对应id标签:

    HTML
    1<circle id="chart5" ... > 2 3<animate xlink:href="#chart5" ... /> 4 5</circle > 6

但省略xlink:href属性值后,动画默认作用于当前位置的父节点标签。

HTML
1<circle id="chart5" class="pie" stroke-linecap="round" fill="none" cx="0" cy="0" r="63.66197723675813" /> 2<animate 3 xlink:href="#chart5" 4 attributeName="stroke-dashoffset" 5 from="400" 6 to="0" 7 dur="6s" 8 fill="remove" 9 repeatCount="indefinite" 10/>

例如上述代码定义了attributeName属性名为:stroke-dashoffset,在6s的时间内从400转换到0,repeatCount表示动画重复次数,fill类似于animation-fill-mode属性定义动画结束后是否回到最初的状态,有两个值:

  1. freeze 表示保留动画结束时的属性值;
  2. remove 动画属性将被移除,默认是remove;
animateTransform

<animateTransform>元素可以执行变换属性的动画。这里的transform与CSS3的transform类似,例如需要执行一个旋转的动画,可以像下面这样定义:

HTML
1 <animateTransform attributeName="transform" begin="0s" dur="2s" type="rotate" from="0deg" to="180deg" repeatCount="indefinite" />
animateMotion

animateMotion元素可以让SVG各种图形沿着特定的path路径运动,例如:

HTML
1<animateMotion path="M10,80" begin="0s" dur="2s" repeatCount="indefinite"/>
利用SVG动画实现一个跳动的心❤️

这里将使用到path标签,可以把path理解成画笔,你只需要给定画笔移动的位置,path将完成绘图工作,path元素的形状是通过属性d定义的,属性d的值是一个“命令+参数”的序列,因为属性d采用的是用户坐标系统,所以不需标明单位,所以使用path绘图的前提是获得图形的:坐标 + 命令 + 参数; path的命令用字母表示,有对应的含义,大写字母表示绝对定位,小写字母表示相对定位,例如:

M 代表“移动到”某个位置
L 代表直线
Z 代表结束闭合路径
···

HTML
1 <path class="path_ract" d="M0,0 L0,150 150,150 150,0 z"> 2 <!--从原点(0 , 0)的位置移动到(0 , 150)、(150,150)、(150,0 ) 再回到原点,并且画直线-->

圆的d值比较复杂,给出画圆的d值公式,圆心坐标(cx, cy) 半径为r:

html
1<path 2 d=" 3 M cx cy 4 m -r, 0 5 a r,r 0 1,0 (r * 2),0 6 a r,r 0 1,0 -(r * 2),0 7 " 8/>
Javascript
1// 圆心坐标(cx, cy) 半径r 2function getcirclePath(cx, cy, r){ 3 return 'M '+cx+' '+cy+' m -'+r+', 0 a '+r+','+r+' 0 1,0 '+(r*2)+',0 a '+r+','+r+' 0 1,0 -'+(r*2)+',0'; 4}

思路如下图所示,先利用path画一个正方形和两个圆,将圆分别向上和向右移动半径的距离,效果如下图所示#codepen地址,整个动画过程是使用SVG SMIL实现,可点击codepen的“return”按钮查看整个画图过程。

代码地址:https://codepen.io/amnEs1a/pen/ZvZOKr/

之前在网上看了很多很酷炫的HTML5/SVG动画,这次自己用代码算是简单体验了一下,SVG确实很强大,好好利用起来,可以创造出很多有意思的事情,希望以上对刚刚接触到SVG的童鞋能有所启发和帮助,这期间翻了很资料和博客内容,包括SVG动画、Canvas和SVG的对比、大漠、张鑫旭写的这方面的文章等等,具体我列在下面的附录列表上;

1.理解SVG的坐标系和转换
Part 1 — The viewport, viewBox, and preserveAspectRatio
Part 2 - The transform Attribute Part 3 - Establishing New Viewports 2.SVG - Scalable Vector Graphics教程
3.大漠老师的SVG系列教程
4.理解SVG的viewport,viewBox,preserveAspectRatio
5.A Look At SVG viewBox and viewport 6.SVG 与 Canvas:如何选择
7.SVG相关目录存档-张鑫旭