UI設計師SVG動畫進階篇——路徑變形動畫(下篇)

泱泱發表於2017-05-17

上篇:juejin.im/post/591272…
中篇:juejin.im/post/591514…

在前面兩篇文章中,雖然完整描述了任意圖形的變形動畫實現方法,但最大的侷限性顯而易見,那就是都是一個圖形變形成另一個圖形,那如果是一變多或者多變一怎麼實現呢?下面就來解決這個問題。

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開頭的路徑曲線的轉化,新增虛擬曲線。
變形動畫全篇終。有問題單獨留言。

相關文章