UI设计师福利之零基础入门SVG路径动画(看最后的更正部分)

3,750 阅读14分钟

先看一个动画效果,这种小飞机沿路径飞行(路径部分线段变成绿色是录屏软件出了问题)。

plane
plane

这种动画效果最常见于发送信息后,两个不同位置之间的导航指向等等,总之使用场景还是很多的。对于SVG动画来说,这种效果是最最简单不过,只需要一个路径外加几个属性的简单设置就能完成,简单到不算飞机图形的话,两句代码,而整个SVG文件只有1K左右大小,我们由浅入深,从基础开始,开启SVG路径动画之门。

我们把动画元素拆解一下,由两个部分组成,一个是路径,一个是沿路径运动的图形元素。

1.path路径获得

关于path路径,SVG官方的定义如下:

  • M = moveto
  • L = lineto
  • H = horizontal lineto
  • V = vertical lineto
  • C = curveto
  • S = smooth curveto
  • Q = quadratic Bézier curve
  • T = smooth quadratic Bézier curveto
  • A = elliptical Arc
  • Z = closepath
    这些还不是最恐怖的,最恐怖的莫过于下面的三次贝塞尔曲线和二次贝塞尔曲线Q。

三次贝塞尔曲线
三次贝塞尔曲线

二次贝塞尔曲线
二次贝塞尔曲线

各位UI设计师们,线性代数可还会?完全不记得了?很好,因为上面这些吓唬人的知识在这个路径动画中统统用不着(你特么是在逗我?(¯﹃¯))(了解贝塞尔曲线的绘图原理多用于曲线非简单规律变化的复杂动画,不过现在有Airb
nb的Lottie神器了,用AE来做复杂的动画转成json文件是捷径,数学大神们除外。)
我们说过了,AI里面的路径在导出SVG时同样会生成对应的<path>标签。如果你是第一次看我的关于SVG动画的文章也没有关系,我们一步步分解。
第一步:AI绘制一条由起点A到终点B的曲线,随便描个边。


AI导出SVG的文件中,仅保留<path>标签部分。我的如下:

<path fill="none" stroke="#78EADF" stroke-width="30" stroke-linecap="round" stroke-miterlimit="10" d="M66.9,517 
c0,0-37.8-194,125.1-161.9S427.6,371.5,350,205.7S439.3,23.4,544,62.2"/>

关于路径描边的各个属性值不再赘述,注意标签中d=""部分,后面我们的路径动画时要调用的就是这个路径。

path路径的参数由AI导出的SVG路径中的d生成。

2.路径动画<animateMotion>

关于路径动画最基础的语法简单到如下:

<animateMotion path="" dur=""/>
<!--dur定义动画时间-->

其中path即为我们在AI中绘制路径自动生成的d="" 所包含的定义路径曲线的部分。
现在,如果我定义一个最简单的圆形circle,加上路径动画属性,已经可以实现动画效果了,代码如下:

<circle fill="#F8B62D" r="20">
<animateMotion path="M66.9,517
c0,0-37.8-194,125.1-161.9S427.6,371.5,350,205.7S439.3,23.4,544,62.2" dur="3s"/> 
</circle>

动画效果如下:

沿路径移动的圆形
沿路径移动的圆形

这里还有一个方法,就是我们给蓝色路径定义一个id,然后路径动画来引用这个id,代码如下:

<!--road为我们定义的路径id-->
<path id="road" fill="none" stroke="#78EADF" stroke-width="30" stroke-linecap="round" stroke-miterlimit="10" d="M66.9,517 
c0,0-37.8-194,125.1-161.9S427.6,371.5,350,205.7S439.3,23.4,544,62.2"/>
<circle fill="#F8B62D" r="20">
<animateMotion  dur="3s">
<mpath xlink:href="#road"/> <!--通过路径链接属性调用定义的路径-->
</animateMotion> 
</circle>

这两种方法均可,重点是第二种方法,后面将要揭秘。

3.任意图形的路径动画

看了上面的是不是感觉so easy,但这肯定不是我们的需求,开始抛砖引玉,比如,我准备做一个沿路径奋力爬行的小瓢虫的动画。先准备素材,在AI中绘制一只小瓢虫,比如图层我命名beatles

绘制甲虫
绘制甲虫

然后得到一堆<g id="beatles">……</g>的SVG代码。
接下来就很简单了,用甲虫的图形来替换上面动画中的圆形,其他不变。得到代码如下:

<g id="beatles">
……<!--此处省略绘制甲虫的代码若干-->
<animateMotion path="M66.9,517
c0,0-37.8-194,125.1-161.9S427.6,371.5,350,205.7S439.3,23.4,544,62.2" dur="3s"/> 
</g>

期待已久的时刻来了,正常思路,我们的小瓢虫可以沿着路径爬行了。各位看官先不要激动,此时,你看到的动画应该是这个样子的(为了方便观察,我把画布调整了大小,留出足够的空间)。

偏移了路线的小甲虫
偏移了路线的小甲虫

小甲虫,你要去哪里?你快回来。再看我们的代码,没毛病,甲虫的位置明明就是摆在路径的起点上。不过我们仍能发现的一个规律是:虽然小瓢虫跑偏了,但跑的姿势似乎还是对的。为什么圆形没事,换个复杂的图形就不可以了呢?我们上面圆形按照路径移动时,并没有定义圆形的圆心位置(cx和cy值),仅定义了半径r。好了,接下来要放大招了,这是你以后制作SVG路径动画的关键。

4.任意图形的位置校正

先来看一句话: 定义了路径动画的图形会把路径的起点作为原点。读起来拗口且难懂,炒个栗子吧。我绘制的路径起点坐标为X=66.9,Y=517,对于了解SVG路径的小伙伴们都知道,其实就是path的起点M值。那对于甲虫来说,(66.9,517)才是它坐标系的原点,而不是(0,0)。如下图所示,整个坐标系向右偏移了66.9px,向下偏移了517px。

偏移后的坐标系
偏移后的坐标系

所以最终的效果就是甲虫沿着偏移后的灰色路径移动。
知道了原因就可以对症下药了。

4.1 方法1——把甲虫拉回它应该在的位置

既然知道偏移的值,再把它放回去,放回去的话就很简单了,我们只要把甲虫移动到画布的左上角原点的位置(即x=0,y=0)

移动图形到原点处
移动图形到原点处

此时再导出SVG,除了甲虫图形代码替换,得到下面的效果:


小瓢虫强势回归!

4.2 方法2——使用SVG的defs元素

这个方法相当于方法1的扩展,你可以使用defs来定义图形,use标签来调用,同样需要图形位于画布原点,代码如下:

<defs>
<g id="beatles">
……<!--此处为绘制瓢虫的代码-->
</g>
</defs>
<use xlink:href="#beatles" x="0" y="0">
<animateMotion  dur="3s">
<mpath xlink:href="#road"/> <!--方法2和方法1由于路径是同一条,所以都适合用调用路径的方法-->
</animateMotion>
</use>

用defs来定义图形的好处是,你可以使用use标签在不同位置重复调用这个图形,比如我下面说明运动速率时摆放的5只小甲虫。
由方法1和方法2我们得出一个结论,图形绘制的时候无所谓位置,只要最后在画布的左上角原点就可以了

4.3 方法3——重新定义移动路径

上面的那个方法虽然可以实现正常路径运动,但实际中存在一个问题(我的动画播放时使用了无限循环,所以看不出)。当动画设置了延迟开始(eg. begin="2s")时,甲虫在动画开始前并不是乖乖的待在起点A,而是移动后的位置,画布左上角,2s后动画开始播放,甲虫才回到A点。来看下面这种解决方案,重新定义图形的移动路径。
跟着我左右右手一个慢动作,右手左手慢动作重播……我们用相同的方法来移动,不过,这次我们移动的是路径,记得选择“复制”,或者干脆点,直接把起点拖到画布的原点。

移动路径
移动路径

然后为了方便区分,建议你给移动后的新路径区分一下描边方法。

新路径
新路径

好了,甲虫按兵不动,导出SVG时,复制后的路径那一堆代码我们只需要d值,我的路径动画代码也就变成了下面的样子:

<animateMotion path="M0,0
c0,0-37.8-194,125.1-161.9s235.7,16.5,158.1-149.3s89.2-182.3,194-143.5" dur="3s"/>

现在,所有都恢复正常,我们的小甲虫又可以乖乖的从A爬到B了,而且无论何时,起点和终点都是我们预期值。(但这个方法有个很大的bug,后面再说)

作为有追求的设计师,这个动画效果是不是感觉low到爆?首先,瓢虫移动速度不符合物理规律,匀速运动,其次,身体没有相应的转动,生硬不生动。好了,来,加上这几个属性,改善一下。

5. 运动速率的设定

先给animateMotion加上下面的代码:

calcMode="spline"  keySplines="" keyTimes="0;1"

calcMode属性定义动画的类型,一共四个属性值,其他不说了,"spline"要搭配后面的keySplineskeyTimes属性一起使用,以上代码的意思是宣告“我要来随心所欲的控制运动速率了!”
keySplines值的设定与CSS的贝塞尔曲线相同。
关于运动速率的贝塞尔曲线(说好了不提它,又来!)有个在线工具可以借助:cubic-bezier.com/

cubic-bezier工具
cubic-bezier工具

这里你可以自定义运动速率曲线并查看效果,不过如果不是需要一些特别的效果,建议使用以下几个固定值:
"慢-快-慢 ease":".25,.1,.25,1"
"线性 linear":"0,0,1,1"这个是默认值,可以不用定义。
"慢开始 ease-in":".42,0,1,1"
"慢结束 ease-out":"0,0,.58,1"
"慢-正常-慢 ease-in-out":".42,0,.58,1"
我做一个甲虫水平移动的动画,来对比一下这五种不同的速率曲线的效果。

不同运动速率曲线的甲虫
不同运动速率曲线的甲虫

同时出发,但中间速度有差,殊途同归,又同时到达终点。
这里我搞了个事情出来,把速率曲线画成了下面这个样子:

任意绘制的曲线
任意绘制的曲线

所以我的keySplines=".28,1.89,.56,-1.32"。现在我的甲虫移动方式是这样的:

自定义移动速率曲线的甲虫
自定义移动速率曲线的甲虫

怎么样,是不是还算有趣。花样可以很多,自行尝试。

keyTimes值(0;1)是最简单的一种,没有对运动过程进行分割,如果你想玩出更深的套路,可以把路径截成好几段,然后定义不同的keySplines值。再炒个栗子。
keyTimes="0;0.66;1" keySplines=".42,0,1,1;0,0,1,1;" 就是路径的前2/3,我希望用ease-in函数定义速度,路径的后1/3线性速度,但在衔接处会抖一下,因为实在使用场景很少,所以keyTimes值就用最基础的就好。

6. 跟随路径曲度的旋转方向

这个简单,只有一句代码:rotate="auto",此时的路径动画已经实现的有模有样了。

跟随路径曲度的图形的旋转
跟随路径曲度的图形的旋转

前面提到过,4.3方法3——重新定义移动路径,有个bug的问题,就是这里,因为旋转方向是依据路径的走向,如果使用移动过后的路径,那旋转的效果简直不忍直视。
这里有一个悖论,方法1和方法2,正常旋转,但初始的甲虫的位置不能在起点,方法3,初始位置在起点,但不能正常旋转。(抓狂ing……)使用过程中选哪个选哪个,好纠结。
好了,设计师们,不用纠结,选1和2,因为……
……
因为……
我们可以用js定义动画开始的时间,哈哈,根本不需要begin属性。

好了,开始放可以复用的代码并注释,一切不能复用的SVG动画代码都是耍流氓!

<svg width="" height="">
<path id="road" fill="none" d=""/><!--路径全部由AI直接生成-->
<g>
……
<!--此处为若干图形代码-->
<animateMotion  dur=""  repeatCount="" rotate="auto"  calcMode="spline" keyTimes="0;1" keySplines="" ><mpath xlink:href="#road"/></animateMotion>
</g>
</svg>

嗯,over,就是介么简单。剩下的只是填空。
比如dur定义全部动画需要的时间,keySplines定义运动速率类型,repeatCount定义播放次数,上面都有一一解释。

那么利用路径动画,都能实现什么效果呢,来继续看:

7.1 路径动画功能扩展——伴随图形变化

现在我要实现一个小甲虫渐行渐远的效果,为了让效果看起来更逼真,我把底图描边路径重新做了一下,然后把keySplines="0,0,.58,1"即运动速率为ease-out慢结束,如下:

渐行渐远的甲虫
渐行渐远的甲虫

这个效果实现也不过再加一句缩放动画代码

<animateTransform dur="" attributeName="transform"  fill="freeze" type="scale" from="1 1" to="0.5 0.5" />

时间与路径动画保持一致,type="scale" from="1 1" to="0.5 0.5"表示宽高缩小到原来1/2。
你可以搞得很复杂,比如伴随甲虫腿的摆动,但涉及到复合动画,好麻烦,这也是我为什么没有用一只带腿的甲虫的原因,哈哈。

7.2 路径动画功能扩展——搭配描边动画+蒙版

结合描边动画和蒙版,我们可以实现下面这种效果:

结合蒙版描边动画
结合蒙版描边动画

一架一路辛苦播撒小豆的飞机。
动画拆解:先绘制一个点状组成的螺旋线路径,然后给这个螺旋线加一个蒙版,把描边动画赋给蒙版,描边路径即为螺旋线路径,颜色为白色,相当于通过蒙版动态画一根白色螺旋线来实现点状螺旋线路径同步显示出来。小飞机就按我们上面介绍过的的路径动画来定义。
感兴趣的话可以把下边的代码拿去复用:

<svg  width="600" height="600" >
<style>
@keyframes dash {
to {
stroke-dashoffset: 0;
}
}
#helix{
stroke-dasharray:2006;  /*2006为螺旋线的长度*/
stroke-dashoffset:2006; 
animation: dash 4s linear forwards;  /*蒙版动画的速率和时间与飞机路径动画保持一致*/
animation-delay:0.2s; /*为了让飞机稍微领先,设置了蒙版动画0.2秒的延迟*/
}
</style>
<mask id="helix">
<!--蒙版调用定义的描边动画helix,d=""包含部分为螺旋线的路径-->
<path fill="none" stroke="#fff" stroke-width="16" stroke-linecap="round" d=""/>
</mask>
<path mask="url(#helix)" id="road" fill="none" stroke="#78EADF" stroke-width="16" stroke-linecap="round" stroke-linejoin="round" stroke-dasharray="0,30" d=""/>
<g id="plane">
…<!--此处飞机代码若干-->
<animateMotion fill="freeze"   dur="4s"  rotate="auto"  calcMode="spline" keyTimes="0;1" keySplines="0,0,1,1" ><!--keySplines值的设定要与描边动画保持一致,如果上面animation定义了ease,则这里也要对应改成".25,.1,.25,1"-->
<mpath xlink:href="#road"/>
</animateMotion>
</g>
</svg>

知识点总结:
①动画的路径直接通过AI绘制后导出的SVG的d值获得。
②图形元素移动到画布原点后再生成对应的SVG代码。
③通过定义calcMode、keyTimes以及 keySplines来修改运动速度。
④定义rotate属性来实现跟随路径曲率的旋转效果。
⑤与其他动画的组合,需要多多的创意。

补充部分(更正):

CSS3有路径动画属性!有路径动画属性!有路径动画属性!在写这篇文章时,一直以为路径动画是CSS3唯一不可取代SIML实现的动画,直到今天,我看到了这个,抑制不住的激动٩(๑>◡<๑)۶,我CSS3终于动画部分大统一了。

@keyframes move{              
0%{offset-distance:0%; }
100%{offset-distance:100%; }
 }
#move{
offset-path:path('');
animation:move 1s ease ;
}

是的,你没有看错,就是offset-pathoffset-distance两个属性。offset-path用于定义路径部分,offset-distance控制元素在路径上的运动距离。

至此,SVG+CSS3动画终于得以完美,所有动画部分均由CSS3来控制,SVG仅用来提供基础图形。因为不记得都在哪些文章中提到过路径动画了,所以没法一一更正,这是初始篇,所以希望所有的阅读这篇文章的人都能看到,不要被我带坑里哦。