雙效合一的SVG多色描邊變形動畫

泱泱發表於2017-08-01

今天在codepen看到一個效果如下:


覺得十分有趣,作者是SVG結合canvas完成的,裡面所有的路徑部分是SVG完成的,但動畫效果是canvas完成的。canvas能看懂一丟丟,能改一丟丟,但不會寫(硬傷~),那就用最熟悉的SVG+CSS3看看能不能完成咯。

1.路徑變形動畫

先來個拆分,動畫是兩部分的結合,流動的描邊和變形動畫。為了和原作者有點區別,我準備做四個形狀的動畫,哦吼吼,升級版!在繪製時恍恍惚惚有種兒童簡筆畫的感覺……

基礎圖形變形過程
基礎圖形變形過程

如果沒有任何的變形動畫基礎,請先移步這三篇文章,瞭解一下變形動畫的實現原理和實操方法(自己推自己的文章,我是該有多臉皮厚呀):

juejin.im/post/591272…
juejin.im/post/591514…
juejin.im/post/59195c…

在AI中如何處理只在這裡簡單概括:
圓形:閉合路徑剪開(頂點),轉成開放路徑,輕微拖動除起始錨點外的三個錨點,消除匯出路徑<path>中小s的存在,使路徑變成標準的小c開頭的路徑。
三角形:閉合路徑剪開(頂點),轉成開放路徑,輕微拖動除起始錨點外的兩個錨點,使路徑變成標準的小c開頭的路徑。(沒錯,我就是傳說中的復讀機君,我有什麼辦法,處理方法滿滿的都是套路啊)
矩形:閉合路徑剪開(左下角),轉成開放路徑,輕微拖動除起始錨點外的兩個錨點,使路徑變成標準的小c開頭的路徑。(關於剪開路徑的位置?這個嘛?沒有為什麼,我就想看看有什麼不同效果啦)
五邊形:閉合路徑剪開(左下角)……(此處省略重複步驟,巴拉巴拉……)

2.多邊形<polygon>轉成<path>小c標準路徑

突然插入這麼一段小直播,是我發現在這個動畫效果中,因為除了圓形,剩下的都是多邊形,其實<polygon points="X1,Y1 X2,Y2 …… "/>這個繪製方法是很容易理解的,都是多邊形頂點對應的絕對座標,但因為有圓形存在,我們不得已才要把很簡單的事情複雜化,然後在AI裡手柄拖來拖去的真的好煩的好嘛,而且有可能匯出的SVG還有大C開頭的,反反覆覆,不勝其煩,那麼有沒有一種簡單的方法可以把這種多邊形路徑直接轉成小c繪製的標準路徑的方法呢?有!
我以五邊形為例,圖示一下:

多邊形與路徑的轉換
多邊形與路徑的轉換

我的五邊形的五個頂點座標依次為X1,Y1 X2,Y2 X3,Y3 X4,Y4 X5,Y5,注意,這裡說的座標都是絕對座標,即在AI中選中錨點之後的X值和Y值。關於具體的轉換,我拿其中一段路徑舉例。我們先看三次貝塞爾曲線繪製路徑的指令,也就是右側綠色的曲線,每一段曲線都由起點和終點兩個端點以及對應的兩個控制點(也就是我們AI中手柄的位置)組成的,而當我們的控制點座標越接近路徑端點,曲線越平,當控制點與端點重合時,就得到了直線。
有了這個概念基礎,理解起來就方便多了,我需要把<polygon>轉換成<path>,首先,起點M的座標(絕對座標)顯而易見就是多邊形頂點的座標,當用絕對路徑C表示路徑1時,起點A控制點座標就是起點A座標,終點B控制點座標就是終點B座標。這樣還不夠,我們需要的是相對座標表示方法的c指令,也就是我喜歡稱之為“標準曲線”的東西。對於小c繪製方法指令而言,起點和終點控制點的相對座標最簡單,就是0,0,但最後一組相對座標則要經過計算,B相對於A的移動距離,也就是終點B的絕對座標與起點A的相對座標差。

當然了,如果你懶得看原理,覺得很煩的話,就可以直接看解決方法,即c0,0 0,0 X(終點-起點),Y(終點-起點)。座標點可以在AI裡面直接獲得,但計算公式還是少不了的。
所以,最終我的五邊形成功的轉換成了<path d="MX1,Y1 c0,0 0,0 X2-X1,Y2-Y1 c0,0 0,0 X3-X2,Y3-Y2 c0,0 0,0 X4-X3,Y4-Y3 c0,0 0,0 X5-X4,Y5-Y4 c0,0 0,0 X5-X1,Y5-Y1">路徑表示方法,這裡說明一下,如果剪開路徑時不錯開,最後一段路徑是大C對應的絕對路徑繪製方法,也就是CX5,Y5 X1,Y1 X1,Y1。

3.新增虛擬曲線

做完上面的工作仍然沒有算完,對於變形動畫而言,曲線的數量要相等才能完成,而我們的這四個圖形,曲線數量分別是:圓→4,三角形→3,矩形→4,五邊形→5,還好,沒有選擇太複雜的圖形,那就給圓和矩形加1個虛擬曲線,給三角形加兩個虛擬曲線,大家全部補齊成5個咯。(什麼?你問我什麼是虛擬曲線?打滾……上面的文章連結你沒看,沒看)
好啦,加過虛擬曲線,處理過的四個圖形的<path>路徑已經統一起來了,這樣就可以套用我們的變形動畫了。
來看一下變形動畫的定義部分:

@keyframes deform{
0% {d:path('');} /*圓形路徑*/
25% {d:path('');}/*三角形路徑*/
50% {d:path('');}/*矩形路徑*/
75% {d:path('');}/*五邊形路徑*/
100%
}
#deform {animation:deform 3s ease infinite};複製程式碼

然後我們的<path>引用這個動畫就好了。就得到了變形動畫:

單純的變形動畫
單純的變形動畫

嗯,只是動了,但起來看上去不是很炫,沒事,go on。

4.流光溢彩動效

關於這種不同顏色沿著描邊路徑流動的效果,我起了個名字叫“流光溢彩”。先拿五邊形為例,看一下單色流動動畫的設定,之所以沒有拿圓形舉例,是怕你想用旋轉來實現啊:

<style>
@keyframes animate {
0%{stroke-dashoffset:0}
100%{stroke-dashoffset:1356} /*五邊形的周長*/
}

#animate{
animation:animate 2s linear infinite;
stroke-dasharray:678;  /*五邊形1/2周長*/
}
</style>複製程式碼

得到效果如下:

單色描邊動畫
單色描邊動畫

原理我簡單解釋一下,dashoffset為虛線偏移位置,dasharray定義了虛線的樣式,只有一個值的話,則表示線長和間距等長,如下圖示:

dasharray和dashoffset
dasharray和dashoffset

當我們把stroke-dasharray定義成1/2周長時,相當於讓圖形實現了一半描邊效果,而CSS中stroke-dashoffset的值的變化,則對應生成了動效,定義差值為周長是為了實現首尾相接連綿不斷的效果。注意一下,這裡說差值為周長,也就是說如果初始0%對應的 stroke-dashoffset如果不是0, 那結束時100%對應的也要變化,這是我們下面實現四個顏色流動的基礎。
這裡如果把stroke-dashoffset的值改成等值負數,會得到相反方向的動畫效果,感興趣的話可以自己試一下。

好了,逐步推進,實現了單色流動,那雙色怎麼辦?要再定義一個動態單色流動動畫,然後進行疊加麼,哎,我們這種懶人總是想方設法偷懶,因為我只要給這個單色流動的動效的底層加一個相同路徑實色描邊,就得到了這種效果:

雙色描邊動畫
雙色描邊動畫

嗯,雙色流動已完成(此為懶人法,非正解,無需掌握,看過算完)。
好了正式進階開始了,上面偷懶法只能解決兩個顏色的問題,當我需要多個顏色,腫麼辦?
嗯,乖乖的多定義幾個描邊動畫設定,去寫CSS屬性吧。因為每個<path>路徑只識別一個描邊效果,那這種多色的只能用多條相同路徑疊加來實現了。我用圖示來表示一下:

多色拼接原理
多色拼接原理

當然了,針對我們四個顏色,如果把相同的五邊形路徑重複四遍是慘絕人寰的,這裡我們可以用<defs>元素或者<symbol>元素來定義需要重複的路徑,然後用<use>元素來引用,推薦<symbol>,是由於<symbol>支援的屬性更多,雖然在這個案例中無法體現出來,但養成好習慣,需要用<defs>的都可以用<symbol>來代替。這裡因為dasharray的定義相同,所以統一到了路徑內聯屬性裡。
來看看程式碼部分:

<svg>
<style>
@keyframes animate1 {
0%{stroke-dashoffset:0}
100%{stroke-dashoffset:1356}/*1356是路徑的長度*/
}
@keyframes animate2 {
0%{stroke-dashoffset:339}/*定義了四個顏色,所以339是1/4周長*/
100%{stroke-dashoffset:1695}/*需要dashoffset變化值是一個周長來實現首尾相接*/
}
@keyframes animate3 {
0%{stroke-dashoffset:678}
100%{stroke-dashoffset:2034}
}
@keyframes animate4 {
0%{stroke-dashoffset:1017}
100%{stroke-dashoffset:2373}
}
#animate1 {
animation:animate1 2s linear infinite;
stroke:#ffb850;
}
#animate2 {
animation:animate2 2s linear infinite;
stroke:#ff7e5d;
}
#animate3 {
animation:animate3 2s linear infinite;
stroke:#8cd2a4;
}
#animate4 {
animation:animate4 2s linear infinite;
stroke:#62adea;
} 
</style>
<symbol><!--用symbol來定義需要重複引用的相同路徑-->
<path id="pentagon" d="" stroke-width="10" stroke-dasharray="339 1017" fill="none"/></symbol>
<use xlink:href="#pentagon" id="animate1"/>
<use xlink:href="#pentagon" id="animate2"/>  
<use xlink:href="#pentagon" id="animate3"/>
<use xlink:href="#pentagon" id="animate4"/>  
</svg>複製程式碼

還算是很清晰的,而且如果用五個顏色,那就初始的dashoffset遞增1/5周長,然後改一下dasharray為線長1/5 間距4/5 就可以了。

得到的效果如下:

四種顏色流動
四種顏色流動

5.雙效合一

獨立設計形狀之間的變形動畫和同一形狀的不同顏色描邊的動畫都已經實現了,現在要做的就是把這兩個效果合在一起了。在我們上面實現“流光溢彩”動效時把需要重複定義的路徑用<symbol>進行了定義,定義的<path>的id值不是被賦予用了某個屬性,而是作為標籤存在,便於被<use xlink:href="#">反覆引用,但當這個效果運用到變形動畫中時,會發現<path>路徑的id對應的是繪製路徑的變形動畫,那我們來換個思路,把這四個路徑當做獨立的存在,每個路徑在進行變形動畫的同時也在進行描邊動畫,此時我們的SVG定義的變形動畫deform的關鍵幀不變,四個不同顏色的描邊動畫的定義animate1-4的關鍵幀也不變,需要變化的是動畫屬性:

#animate1 {
animation:deform  4s ease  infinite, animate1 2s linear infinite;
stroke:#ffb850;
}
#animate2 {
animation:deform  4s ease  infinite, animate2 2s linear infinite;
stroke:#ff7e5d;
}
#animate3 {
animation:deform  4s ease  infinite, animate3 2s linear infinite;
stroke:#8cd2a4;
}
#animate4 {
animation:deform  4s ease  infinite, animate4 2s linear infinite;
stroke:#62adea;
} 複製程式碼

即每個路徑的動畫屬性同時賦予了兩種動效,一個是變形的deform動畫,一個是對應的描邊動畫。
為了儘可能的優化程式碼,我把相同定義的<path>屬性統一定義到了CSS裡面,如下:

path{stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-width:10;stroke-dasharray:339 1017;fill:none}複製程式碼

這樣,我們的對應的四條路徑的程式碼就簡化成了如下:

<path id="animate1" />
<path id="animate2" />
<path id="animate3"/>
<path id="animate4"/>複製程式碼

這裡如果有好奇的小夥伴可能會提出疑問,我們的描邊動畫在定義時用的周長是五邊形的周長,但這個動畫裡的幾個形狀並不是等長,腫麼辦?
其實不要理會,只要選一個最長的路徑進行定義就可以了。因為我們的路徑是一層層疊加的,如果圖形的周長比定義時選擇的短,出現的結果就是最頂層的路徑會略長一些,但對於這類動畫而言,很難看出差別。

雙效合一動畫
雙效合一動畫

另外這裡如果對變形的效果不滿意,可以自行調整路徑的方向和起點位置,以前的文章裡都有詳細的方法,不再贅述。
當然了,手癢癢的我還是改了一下各個引數,看了一下效果,比如我定義了stroke-dasharry:100 300 (線長100 間距300的虛線),同時改了其他的stroke-dashoffset的值,依次差階100,然後得到了一個效果:

重新定義虛線樣式後的效果
重新定義虛線樣式後的效果

即使得到了相要的動畫效果,但積極努力追求上進的我卻依然不滿意啊,因為我想讓變形動畫在完成一個變形之後略作停留之後再進行下一個變換。而不是像現在這種唰唰唰一氣呵成,於是乎,我改進了一下,得到了下面這種效果:

修改關鍵幀之後動畫效果
修改關鍵幀之後動畫效果

我是用了偷懶的效果,把變形動畫的關鍵幀改成了下面這種:

@keyframes deform{
0% {d:path('')} /*圓形路徑*/
15% {d:path('')} /*三角形路徑*/
25% {d:path('')} /*三角形路徑*/
40% {d:path('')} /*矩形形路徑*/
50% {d:path('')} /*矩形形路徑*/
65% {d:path('')} /*五邊形路徑*/
75%{d:path('')} /*五邊形路徑*/
90% {d:path('')} /*圓形路徑*/
100% {d:path('')} /*圓形路徑*/
}複製程式碼

嗯,滿意。
直接附上codepen的地址,
codepen.io/yangyangbei…
小夥伴們自行檢視咯。

相關文章