UI设计师SVG动画进阶篇——路径变形动画(下篇)

3,608 阅读22分钟

上篇:juejin.cn/post/684490…
中篇:juejin.cn/post/684490…

在前面两篇文章中,虽然完整描述了任意图形的变形动画实现方法,但最大的局限性显而易见,那就是都是一个图形变形成另一个图形,那如果是一变多或者多变一怎么实现呢?下面就来解决这个问题。

8.不同数量之间图形的变形动画

以最简单的例子来说明,我要做个一个水珠裂变成两个水珠的动画效果,裂变过程毫无疑问属于图形的变形过程。但是裂变之后分开成两个的时候怎么办?这是我们首要解决的问题。先说一下各种实现思路,然后进行对比。

8.1 方法1——分割法(断开路径)

我们在前面说过用剪刀断开锚点,使闭合路径转换成开放路径,所以聪明的设计师小伙伴应该可以想到,既然可以断开一个锚点,那就可以再断开一个,让一条路径变成两条路径,两条路径分别实现自己的动画效果,换句话说,就是半个小水珠变成一个小水珠的变形动画,如下图所示

分割法实现变形动画思路
分割法实现变形动画思路

剩下的工作就按部就班,给半水珠增加锚点,调整所有路径的手柄,自己定义起点和路径方向。


这里需要注意一点,对于我们前面的起点和终点重合的开放路径,锚点数=路径数,但当水珠分割成两条路径后,由于起点与终点不重合,所以锚点数+1=路径数,也就是说,我们要给半个小水珠的开放路径多增加一个锚点,此外,对于这种起点终点不重合的开放路径,AI导出的SVG的d值是以小c结尾的,不同于起点终点重合的大C结尾,处理方法仍然很简单,只需要断开路径的地方让起点与终点错开一点就可以了,视觉上是没有影响的。下图就是我处理的变形后的水珠的顶部。

处理后错开的起点终点
处理后错开的起点终点

同时我们动画模板那里要改一下,多增加一个变形动画,并且两个<path>各调用一个变形动画,代码如下:

<svg>
<style>
@keyframes deform1{
0% {d:path('');}       /*变形前左半个水滴的路径*/
100% {d:path('');}     /*变形后左边水滴的路径*/
}
@keyframes deform2{
0% {d:path('');}     /*变形前右半个水滴的路径*/
100% {d:path('');}   /*变形后右边水滴的路径*/
}
#animate1 {animation: deform1 2s ease;}
#animate2 {animation: deform2 2s ease;}
</style>
 <path id="animate1"/>
<path id="animate2"/>
  </svg>

那看一下这种实现思路得到的效果如何

虽然表面上看实现了1变2,但一分为二的过程极其生硬,主要原因是我们实现思路就是半个水滴到一个水滴,所以动画过程完全复现了我们的思路。

8.2 方法2——重叠法(“一变一”+“一变一”)

上面方法实现的效果并不好,而且设想一下,这是变成2个,那如果是变成3个,4个呢,要把变形前的图形切的稀碎么……
既然我们前面两篇都是一变一的实现方法,那么我们不妨变通一下,比如变形前的水滴实际上有两个完全重合的图形,一个变成左边的,一个变成右边的。实现思路如下:

重叠法变形动画思路
重叠法变形动画思路

虽然两个水滴是重合的,但是路径方向那里,向左变形和向右变形的原始路径方向一定要是相反的,否则你将得到下面这种效果(你甭说,就这种误打误撞的错误效果,做个打开的门啊,书啊的,居然还不错):


那即使是对称变换,看下效果又如何呢:

重叠法实现的裂变动画
重叠法实现的裂变动画

看上去完全不像变形动画对不对,明明是两个位移动画的拼接。而且改来改去的辣么麻烦,早知道这种位移动画,直接来个按X轴移动的动画就好了。

先不要把这个方法一棍子打死,那是因为我们变形前后的图形形状没有变化,才会产生位移效果,如果是做一个圆形变成两个水滴,变形效果还是有的,如下:

圆形变水滴的动画
圆形变水滴的动画

而且变成3个甚至更多,也毫无违和感:

圆形变成多个水滴的动画
圆形变成多个水滴的动画

重叠法也是种“障眼法”,当你的形状是实色填充时,OK,没有问题,但当你填充的是半透明色或者是描边呢,你将得到下面这种:

重叠法实现的描边效果的变形动画
重叠法实现的描边效果的变形动画

效果一点都不欢乐,直接暴露了我们妄图掩盖的事实真相。
当然了,方法2虽然不是最佳,但某些情景下也是适用的。
不过我们重磅推出的是下面这个方法。

8.3 方法3——拼接法(找到裂变的临界点)

此方法堪称方法1和方法2的结合,我们方法1是把原始形状生生的切成两半,那这个方法是我们找变形后图形一分为二的那一瞬间,比如针对这个变形效果,就是两个变形后水滴仍然相连的刹那,此时,仍然是一个图形,动画分解成两个阶段,第一阶段,藕断丝连,第二阶段,快刀斩下。但在瞬间,运用了“障眼法”,来实现两个过程的完美拼接。
那我们要做的,就是在AI里用路径查找器,把临界点的两个形状进行合并。我用图示表示一下

拼接法变形动画思路
拼接法变形动画思路

2和3,看上去是完全一样的,但放大来看,2是一个图形但3是两个图形叠放在一起,第一阶段就是1→2,这个我们很轻松就能实现,无非给1补充锚点,第二阶段3→4,这个实现方法多样,你用位移动画也行,用变形动画也行,利用2和3的完全一致性,把握好时间拼接点,无缝对接。
为了省事,位移的那里我也直接用定义路径变形动画来实现,代码如下:

<svg>
<style>
@keyframes deform1{
0% {d:path('');} /*原始水滴*/
100% {d:path('');}/*临界点水滴*/
}
@keyframes deform2{
0% {d:path('');} /*重叠效果的左边水滴*/
100% {d:path('');} /*移动位置后的左边水滴*/
}
@keyframes deform3{
0% {d:path('');} /*重叠效果的右边水滴*/
100% {d:path('');} /*移动位置后的右边水滴*/
}
#animate1 {animation: deform1 1.5s cubic-bezier(0.8, 0, 0.85,0.5);}
#animate2 {animation: deform2 0.5s cubic-bezier(0.15, 0.5, 0.2, 1) 1.5s;}
#animate3 {animation: deform3 0.5s cubic-bezier(0.15, 0.5, 0.2, 1) 1.5s;}
</style>
<path id="animate1"/>
<path id="animate2"/>
<path id="animate3"/>
</svg>

简单解释一下,2s的动画效果我拆分成2部分,前一部分变形效果1.5s,第二部分位移0.5s,但要延迟1.5s后执行,以实现时间上的无缝对接。
修改运动速率,是因为ease表示慢-快-慢,如果拼接动画都使用这个运动速率,整个流程下来就是慢-快-慢-慢-快-慢,在临界点那里会有明显的停顿感,因此我修正了运动速率以消除停顿效果。本来用了预定义的值,变形动画为ease-in(慢—快),而位移动画为ease-out(快-慢)。但发现衔接部分仍然不够顺滑,索性自己重新写了速率曲线,说着吓人,方法掌握之后其实很简单,正好借这个案例说一下:

运动速率曲线分割方法
运动速率曲线分割方法

首先我绘制了一个ease曲线,然后在中间位置一分为二,分别作为两个动画的速率曲线,cubic-bezier值对应的是控制曲率的手柄的两组坐标点(正常坐标系且X和Y最大为1,非笛卡尔坐标系),即我在图中用红点标出来的部分,然后就可以轻松的获取坐标值了。

来看下效果如何:

拼接后的变形动画
拼接后的变形动画

为了证明我们这个效果是有效的,我改成描边效果,来看一下:

描边效果
描边效果

作为设计师的你,仍然会对这个效果不满意,理由很简单,我们都知道水的表面是有张力的,且是柔性的,这种动效则缺少一分为二的那种粘连感。
我们重新定义一下临界点的形状,在AI中操作起来极为便利,不过向两侧移动下左侧和右侧的锚点

重新定义临界点形状
重新定义临界点形状

得到这么辣眼睛的效果纯属意外……
然后对这个形状进行分割,什么工具都可以,剪刀,刻刀,随意,分割成两个部分之后,再变形成最后的左右水滴,得到效果如下:

完美的水滴裂变效果
完美的水滴裂变效果

经过层层分析(分明就是我自己不停的掉坑里,爬出来,继续……),终于得到了理想的变形效果。

小总结,之所以没有从一开始就直接来方法3,是因为方法3费时最长,其实我们需要的是越简单的方法实现越理想的效果越好(拗口),所以在一些场景中,能用方法1和方法2(尤其方便)来实现实则再好不过。
对于方法3这种寻找临界点的图形,则更适合精细化制作。

小技巧:由于这类变形动画涉及的形状及位置比较多,为了方便自己查看SVG代码,实际展现的色值信息由于是定义在CSS样式中,所以建议作图时定义成方便识别的颜色,或者建在不同图层上,(AI导出的SVG会根据不同图层进行分组)。

9.镂空图形的变形动画

截止到这里,我们已经可以实现无穷的变形效果了,裂变,组合,玩到嗨起来。但还有一种常见的图形,需要单独说一下实现变形动画的方法,就是下面这种镂空的图形,这次的案例是从一把钥匙变成一把锁,只需要这一个案例做基础,掌握方法之后,一通百通,原始图片如下:

有镂空的图形
有镂空的图形

如果没有钥匙孔和锁孔,这种变形动画实现起来可谓简单无比,但多了孔之后,貌似有点棘手,别急,先来看这种图形(AI中需要合并形状)d值的面目。由于案例图形太过复杂,我们还是从最简单的入手。

最简单的镂空形状的d值分析
最简单的镂空形状的d值分析

从d值中可以看出来,AI在导出SVG时生成了两个路径,上面对应底层路径,下面对应镂空形状的路径,这不就是AI里减去底层形状的意思嘛,SVG对于这种包含多个路径的形状自动解读成第一个路径为底层形状,其他形状都执行减去底层形状的操作。了解规则后,再来完成这种有镂空图形的变形动画自然水到渠成。
对图形的改造套用我们最基础的动画模板,然后把含有两个路径的d值对应填进去,会得到下面这种效果:

钥匙变成锁
钥匙变成锁

从最终效果中能明显看出来,镂空部分和底图部分分别执行了两个变形动画。那最大的问题也在这里。变形动画的过程中填充色会超出正常的变形区域。那如何来改善呢?

10.使用变形蒙版的变形动画

这里借用以前提到的蒙版来试一下。
UI设计师对于蒙版丝毫不陌生,SVG蒙版的原理是一样的,镂空部分我用黑色蒙版来处理,达到同样的效果。
思路如下:动画拆解成两部分,一部分是无镂空部分的图形的变形动画,另一部分我把变形动画附加给蒙版黑色部分,即我们要镂空的区域。

蒙版变形动画
蒙版变形动画

与上面简单的利用d值里包含的两个路径对应变形到另外两个路径不同,我们把镂空部分单独用一个蒙版变形动画来实现,首先,根据蒙版的原理,绝对不会有填充色超出变形区域的情况,其次,这样赋予了这类变形动画更多的自由度,比如可以设置不同的时间和动效等等。
我把代码和注释贴上来简单解释一下:

<svg>
<style>
@keyframes deform1{
0% {d:path('');}/*变形前底图*/
100% {d:path('');}/*变形后底图*/
}
@keyframes deform2{
0% {d:path('');}/*变形前蒙版黑色部分*/
100% {d:path('');/*变形后蒙版黑色部分*/
}
}
#animate1 {animation: deform1 3s ease;}/*
底图变形动画*/
#animate2 {animation: deform2 3s ease ;fill:#000000;}/*
蒙版黑色区域变形动画*/
</style>
<mask id="hollow"><rect x="0" y="0" fill="#ffffff" width="" height=""/><path id="animate2" /></mask><!--定义一个白色矩形为底,黑色为变形动画的蒙版hollow-->
<path  id="animate1" mask="url(#hollow)"/><!--底图变形动画上附加一个蒙版变形动画-->
</svg>

看到这里,没有任何SVG蒙版基础的UI设计师或许会有些发憷,但还是希望了解一下,因为蒙版动画很强大,后面会开单独的专题。
来看一下利用蒙版变形结合底图变形的动画效果如何:

蒙版变形+底图变形
蒙版变形+底图变形

很明显的,不会出现填充色超出区域的问题,我取两个不同实现方法动画的同步一帧对比如下:

两种方法对比
两种方法对比

前面提到过,把蒙版动画单独定义自由度更高,比如,我再给蒙版变形动画增加一个50%的关键帧,然后缩小成极小的一个点,动画效果就变成了下面这种:

蒙版变形改进后的动画
蒙版变形改进后的动画

似乎更改进了一些。
对比得出的结论是蒙版变形来实现更优化,但实际使用时,尽量采用两个路径同步变形这种,为什么?简单呐,先看效果,不满意再采用蒙版变形动画。

12.路径曲线值的转换

我们对图形的操作一直说的都是锚点手柄部分处理,一直想避开这部分,主要是考虑到UI设计师毕竟对AI软件更熟悉,操作起来更方便,而是我在做案例的实际操作过程中,发现对于锚点较少的情况还好,找对应路径也能顺利找到,而在我上面钥匙和锁变形动画中(20个锚点),即使通过d值看出来有些曲线时非小c开头的,但数起来真的很困难,所以额外增加这一部分,可以直接通过修改d值实现同一成小c曲线转换的方法。只说方法,不说原理哦。

12.1小s→小c

先说最容易出现的小s,看过上篇的设计师们已经知道产生小s的方法是因为转换点直接拖拽,而且s后面只有四个值,转化方法如下:
sA,B,C,D←→c前一组倒数第二个数减去倒数第四个数,前一组倒数第一个数减去前一组倒数第三个数,A,B,C,D
啊啊啊,头晕了对不对,这还是仅限于前面是正常一组小c开头的曲线的转换公式,所以除非你只有极个别小s出现,否则多算伤身啊。

12.2小l→小c

设计师小伙伴们一定擦亮眼睛,因为小l与数字1太像,但只要记得我们的d值只支持一位小数,而出现类似3.51这样的数据时,那一定是3.5和小l。小l是绘制直线的命令,后面只有两个值,转换方法如下:
lA,B←→c0,0,A,B,A,B
前面补两个0,后面复制一组小l后面的两个值。
小l和小s最常见,下面这两个则出现的情况少一些,一点出现,建议改一下图形,为何,来看转化方法。

12.3小h→小c

h为水平直线绘制命令,后面只有一个值,转换方法如下:
hA←→c0,0,A,前一组路径曲线小c的最后一个值,A,前一组路径曲线小c的最后一个值

12.4小v→小c

v为垂直直线绘制命令,同上同理,转换方法如下:
vA←→c0,0,前一组路径曲线小c的倒数第二个值,A,前一组路径曲线小c的倒数第二个值,A
看,诚不欺你,所以为了不给自己制造困难,稍微挪挪锚点,不要那么水平垂直的直来直去嘛。
还有出现的情况是绝对定位大写字母开头的,这个转换起来,啧啧,反正不借助工具,我是不能接受手动计算的。
就酱。
借助公式,顺便做个动画效果看看。

拔地而起的小蘑菇
拔地而起的小蘑菇

想做个拔地而起的小蘑菇,我只需要AI导出的SVG里对应的蘑菇的d值就可以了,直线呢?有了上面的公式,我来手动改造,连锚点都懒得在AI里加了,反正也不会导出我们需要的d值,还要一通调整,太麻烦。那怎么改呢?先数数,蘑菇一共6段曲线组成,好了,我只需要知道直线路径的起点坐标,我的是(60,490),那我通过手动,把直线的d值改造成下面这种:

d:path('M60,490,c0,0,50,0,50,0,c0,0,100,0,100,0,c0,0,50,0,50,0,c0,0,50,0,50,0,c0,0,50,0,50,0,c0,0,50,0,50,0')

这就是相对坐标的好处,c0,0,50,0,50,0,表示一根水平的宽度50的直线,对应蘑菇的6段曲线,重复6次就OK了。

蘑菇动画
蘑菇动画

虽说一副软趴趴的样子,但好歹站起来了。
原来这种调整方法也蛮好用嘛,妈妈再也不用担心我调整手柄拖拽到吐血了。

13.关于添加虚拟曲线

再来一根直线变飞鸟与鱼的,哦,不是飞鸟,是肥鸟。把前面的知识点借机再唠叨一遍。顺便带出来压箱底的技能,虚拟曲线。先卖个关子。


看到这三者之间的变化,心里先抖一抖,每个形状都不同。先来最复杂的吧,肥鸟先断开闭合路径,然后起点终点错开,以便生成小c结尾的相对坐标绘制的曲线。鱼原来只有4段曲线,肥鸟有9段,按理说应该哒哒哒给鱼补上锚点,停停停,就是这里。现在插播一个重磅武器,虚拟曲线c0,0,0,0,0,0。虚拟曲线是我给它的命名(怎么,不可嘛),这货实际是不存在的,它存在的价值,聪明的设计师小伙伴一定可以猜出来,就是来补充缺失的曲线的。而且相当具有跟随性,你把它放在哪里,它就是前面曲线的终点。
这个例子中,鱼有4段曲线,好了,我们的虚拟曲线该上场了,所以我把直线里面塞进3三段虚拟曲线即增加3个c0,0,0,0,0,0,d值变成了下面这个样子:

d:path('M40,130,c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,280,0,280');

其实这个效果和

d:path('M40,130,c0,0,0,280,0,280');

是一样的,我们只是在欺骗浏览器,制造一些不存在的曲线以便弥补变形前后图形曲线数量的不同。
动画可以拆成两部分,衔接起来,也可以像我这样在一个动画里完成,由于最终小胖鸟的曲线数量最多,多以最终我给直线补充了8段虚拟曲线,给小鱼补充了5段(全部放到鱼嘴的位置),然后看一下效果

直线-鱼-肥鸟变形动画
直线-鱼-肥鸟变形动画

是不是感觉太简单了,而且很愤怒的指出来,为什么到最后才说这种简单好用的方法?!勿燥,理由很简单,加锚点是为了让变形的过程变得均匀。比如我的小鸟变成鱼,如果是通过分散添加锚点来实现,效果是下面这样的:

均匀添加锚点后的变形效果
均匀添加锚点后的变形效果

所以,即使你用这种偷懒的方法,如果不是为了某种特殊效果,也最好把c0,0,0,0,0,0散布开,穿插在其他曲线之间。但有时候,你可以利用这种虚拟曲线随心所欲的实现希望达到的效果。刚才做的拔地而起的蘑菇,我们说它软趴趴,是因为直线被均匀分割成6段宽50的直线,每个直线对应向组成蘑菇的曲线变换。那怎么才能让变形效果变得“刚硬”起来,比如从中心点拔地而起。这时,要换种思路对直线进行改造。

直线改造计划
直线改造计划

既然需要中心点,那么第一步,从中间一分为二是少不了的,相当于从起点开始,有2条宽150的水平直线,此时直线的d值如下:

d:path('M60,490c0,0,150,0,150,0c0,0,150,0,150,0')

然后把需要增加的4条虚拟曲线塞进去,位置就是中心点,也就是第一段路径后面。

d:path('M60,490c0,0,150,0,150,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,150,0,150,0')

好了,看看是不是从中心点长出的蘑菇:

补钙后的蘑菇~
补钙后的蘑菇~

看这硬朗的变形效果,完美!

14.利用路径变形动画实现“描边”动画

svg描边动画效果在别的文章里介绍过,利用路径变形动画依旧可以完成,只不过这只是种“伪描边”,更像是从某个点辐射出去的效果,看一下下面这个动画:

一株盛开的花
一株盛开的花

这就是我用变形动画实现的。

动画思路拆解
动画思路拆解

边看图边说,既然我们说过可以添加虚拟曲线,那么再延伸一下,当一段路径只有起点M值,剩下全是c0,0,0,0,0,0组成,那么不管有多少段虚拟曲线,实际这只是一个虚拟点,但这个点看不见,却是有坐标的,坐标就是M值。
所以我们这个动画制作过程就简单多了,主要获取我图中红圈圈出的四个点的坐标就可以了。我实际绘图的颜色是左上角那个“绝美”的配色,理由很简单,为了方便自己区分啊,左边叶子色值#FF0000,中间花茎#00FF00,右边叶子#0000FF,只看FF值就可以轻松分辨出对应的路径了。
比如我们左边叶子的绽放点坐标值为(X1,Y1),叶子有2段曲线路径,那变形前的虚拟点d值对应为MX1,Y1c0,0,0,0,0,0c0,0,0,0,0,0,就能轻松实现变形效果了。
代码的简化版即注释如下:

<svg>
<style>
@keyframes deform1{
0% {d:path('');} /*花茎变形前虚拟点*/
100% {d:path('');}/*花茎曲线路径*/
}
@keyframes deform2{
0% {d:path('');}/*左边叶子变形前虚拟点*/
100% {d:path('');}/*左边叶子曲线路径*/
}
@keyframes deform3{
0% {d:path('');}/*右边叶子变形前虚拟点*/
100% {d:path('');}/*右边叶子曲线路径*/
}
@keyframes deform4{
0% {d:path('');}/*花朵变形前虚拟点*/
100% {d:path('');}/*花朵曲线路径*/
}
#animate1 {animation: deform1 1s ease forwards;}/*长出花茎时间1s,forwards表示动画状态停留在结束状态*/
#animate2 {animation: deform2 1s ease 1s  forwards;}/*长出左边叶子时间1s,延迟1s(即花茎动画时间)开始*/
#animate3 {animation: deform3 1s ease 2s  forwards;}/*长出右边叶子时间1s,延迟2s(即花茎+左边叶子动画时间)开始*/
#animate4 {animation: deform4 1s ease 3s  forwards;}/*开花时间1s,延迟3s(即花茎+左边+右边叶子动画时间)开始*/
</style> 
<path  id="animate1"/>
<path  id="animate2"/>
<path  id="animate3"/>
<path  id="animate4"/>
</svg>

这里再提供一个转换方法,就是关于绝对位置大C绘制路径。如果你导出的d值中有大C开头的曲线,关于这种通过虚拟点变形的话,就没有必要再去调整路径了。
c0,0,0,0,0,0等同于CX1,Y1,X1,Y1,X1,Y1。X1和Y1就是你的起始点M值的坐标。关于SVG路径path的贝塞尔曲线绘制方法的介绍网上很多,耐心看一遍就知道转换的原因了。

实际使用过程中,一定要把握的思路是尽量用最简单的方法来实现动效,已经开始着手准备写一篇SVG微动效的文章,毕竟SVG结合CSS3实现的动画不可与真正的动画制作软件同日而语,应用最多的场景,应该是一些微动效。
这篇作为变形动画的进阶篇,涉及的知识点比较杂,包括拼接动画临界点的定义及自定义速率曲线实现无缝拼接的方法,结合蒙版变形动画实现变形动画,非小c开头的路径曲线的转化,添加虚拟曲线。
变形动画全篇终。有问题单独留言。