UI設計師福利之零基礎入門SVG路徑動畫(看最後的更正部分)

泱泱發表於2017-05-04

先看一個動畫效果,這種小飛機沿路徑飛行(路徑部分線段變成綠色是錄屏軟體出了問題)。

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僅用來提供基礎圖形。因為不記得都在哪些文章中提到過路徑動畫了,所以沒法一一更正,這是初始篇,所以希望所有的閱讀這篇文章的人都能看到,不要被我帶坑裡哦。

相關文章