四個 SVG 動畫應用範例 - Firefox 十週年活動網站技術解密

不知道大家有沒有注意到,這次的 Firefox 十週年活動網站上出現了一些特殊的動畫元素。這些元素不是 GIF 圖素,也不需要複雜的動畫套件,而是運用了 SVG 本身具備的動畫功能。

使用 SVG 來做動畫有幾個好處:

  • 首先因為是向量圖形,所以任意縮放變形都不怕出現鋸齒顆粒。
  • 多數向量繪圖軟體都支援 SVG 輸出,詳細支援度可參考 Wikipedia 上的比較表
  • 除了進行一些數值型的動畫漸變(位置、顏色等)以外,也支援路徑和形狀漸變。
  • 可內嵌於網頁中,並搭配 CSS/Javascript 進行樣式變化、設定動畫、互動功能等。
  • 支援導引線動畫。
  • 可透過
    xlink:href
    屬性重用元素。

以下的四個範例將會一一示範這些 SVG 的特異功能。

線條繪製動畫

See the Pen SVG Line Drawing Animation by Eddie Lin (@yshlin) on CodePen.

可以看到幸福的狐家元件活動網頁裡,在往下捲動的過程中,附加元件的圖示線條流暢地繪製出來,這是如何做到的呢?首先當然需要 SVG 圖素,這部分需要先請設計師在向量圖軟體中繪製完成,再透過軟體匯出為 SVG 檔案。有了 SVG 檔案以後接下來的就簡單了,只需要運用兩個 SVG 圖形元素的屬性:

stroke-dasharray
 、
stroke-dashoffset
 。

stroke-dasharray
的用途是指定線條的虛線樣式,通常由一組數字陣列所組成,在繪製虛線時會依據陣列內的數字決定線條或間隔的長度,詳細的使用方式可參考 MDN 說明文件,在這裡只需要用到最簡單的單一值用法,簡單來說
stroke-dasharray="5"
代表了虛線會以 5px 的長度及 5px 的間隔來繪製,那麼如果 
stroke-dasharray
內指定的長度完全和線條的長度一樣(或更長),會發生什麼事呢?我們會看到完整的實線被繪製出來,並且不會有任何短少或接縫出現,例如線條長度有 100px,而
stroke-dasharray="100"
,則我們會得到和原本沒設定時一模一樣的 100px 實線。

那麼為什麼要做這樣的設定呢?其實我們是要運用虛線接下來的 100px 間隔來幫助我們完成繪製動畫。設定了 

stroke-dasharray="100"
之後,後面的 100px 間隔並非不存在,只是目前的位置看不到罷了。因此我們只需要再運用 
stroke-dashoffset
屬性,改變虛線繪製的啟始位置即可。比如說設定
stroke-dashoffset="50"
,我們會看到 100px 線條只畫出了一半,而當 
stroke-dashoffset="100"
時,我們發現整個線條都不見了!

這樣一來我們只要透過 SVG 的 

animate
元素設定,或是 CSS、Javascript 來漸進式地改變 
stroke-dashoffset
的數值,就可以做出線條繪製的動畫效果囉!以下示範 CSS 動畫的設定方式:
@keyframes draw {
  to {
    stroke-dashoffset: 0;
  }
}

.logo-outline path {
  animation: draw 3s linear infinite;
}

可以看到 CSS 動畫中除了針對一般 HTML 屬性以外,也可對 svg 圖形特有的屬性進行漸變。這裡建議在 SVG 路徑內的

stroke-dasharray
 、
stroke-dashoffset
上設定和線條總長度一樣的值,比如說
stroke-dasharray="100" stroke-dashoffset="100"
,並在
@keyframes
中指定
to
,讓
stroke-dashoffset
的數值漸變為 0,這樣就能達到繪製線條的動畫效果,且指定
to
的好處是
@keyframes
設定可以重用在總長度不同的路徑圖形之上,不需要為了各別的路徑分別作設定。

這邊比較麻煩的地方是需要預先取得線條的總長度,如果線條是

circle
rect
line
等等簡單形狀的話,長度可以透過簡單的公式自己算出來,如果是 
polyline
的話稍微麻煩一點,線段少的就當作多條 
line
  來運算加總,線段多的可能直接用目視猜測慢慢逼近。那麼如果是包含複雜曲線的 
path
元素呢?其實這反而相對簡單,只需要利用 Javascript 取得元素之後,呼叫 
getTotalLength()
函式即可,有時候 
getTotalLengh()
取得的數字會有些微的誤差,這部分要再視圖形的狀況個別調整。

另外如果想要讓線條繪製動畫可以配合捲軸動畫來運作,可以參考活動網站的作法(Github 連結在此)。

多影格動畫

See the Pen SVG Waving Smoke by Eddie Lin (@yshlin) on CodePen.

SVG 動畫很方便的地方在於,我們只需要設定形狀或路徑的關鍵畫格(keyframes),它會自動補齊中間的漸變動畫。這個範例中要做的是類似卡通裡波浪式的冒煙效果。首先我們需要四組 S 型路徑以組成一組波浪動畫:

這個範例的四組路徑的定義值分別為

d="M 5 5 c -3 2, -3 8, 0 10 s 3 8, 0 10"
d="M 3 5 c 0 3, 4 7, 4 10 s -4 7, -4 10"
d="M 5 5 c 3 2, 3 8, 0 10 s -3 8"
d="M 7 5 c 0 3, -4 7, -4 10 s 4 7, 4 10"
。接下來要做的就是將它們加入動畫的關鍵畫格設定中,讓我們在
path
元素內加上一個 
animate
元素如下:
<path id="smoke" fill="none" stroke="#999999" stroke-width="6" 
      stroke-linecap="round" stroke-linejoin="round">
  <animate attributeName="d"
           values="M 5 5 c -3 2, -3 8, 0 10 s 3 8, 0 10;M 3 5 c 0 3, 4 7, 4 10 s -4 7, -4 10;M 5 5 c 3 2, 3 8, 0 10 s -3 8, 0 10;M 7 5 c 0 3, -4 7, -4 10 s 4 7, 4 10;M 5 5 c -3 2, -3 8, 0 10 s 3 8, 0 10"
           dur="1s"
           repeatCount="indefinite"/>
</path>

attributeName
是用來指定要漸變的屬性,這裡設定
attributeName="d"
以進行路徑值的漸變。通常簡單的情況我們只在 
animate
元素上加上 
from
  或 
to
屬性,讓圖形只針對頭尾兩組設定值進行漸變,但在這個範例中需要多個關鍵畫格才能讓動畫流暢地漸變,因此我們改用 
values
  屬性。
values
屬性的用法如上例,只要用分號 ; 來區隔每個關鍵畫格的設定值即可,要注意的是為了讓動畫能夠完成循環,最後一個關鍵畫格要和第一格一樣,因此範例中有五組關鍵畫格設定。
dur
屬性是設定動畫的時間長度,如果像現在有五組關鍵畫格設定的話, 
dur
可以針對中間產生的四組漸變區段各別設定不同的時間長度,在這個範例中只設定了一個總長度
dur="1s"
,如此一來三個區段的時間長度就會平均分配,也就是變成每一段 0.25 秒。最後因為要讓它無限循環所以要設定
repeatCount="indefinite"
 。

有了一條波浪後,接下來就是想辦法複製三條出來。為了將來維護方便,我不希望同樣的東西被複製貼上三次,以免到時要更改設定時要改三次。這時就可以使用

xlink:href
屬性。首先在 svg 元素上加上 
xmlns:xlink
屬性以引用 
xlink
的命名空間定義:
<svg version="1.1" 
     xmlns="http://www.w3.org/2000/svg" 
     xmlns:xlink="http://www.w3.org/1999/xlink">

接著在波浪的 

path
元素底下加上兩個 
use
元素:
<use xlink:href="#smoke" transform="translate(10,0)" />
  <use xlink:href="#smoke" transform="translate(20,0)" />

xlink:href="#smoke"
透過 
path
的 id 來引用波浪動畫路徑,達到複製的效果,而
transform="translate(10,0)"
則是指定複製出來路徑的位移,這邊設定讓三條波浪動畫水平間隔 10 像素排列。如此一來就完成冒煙的動畫啦!

導引線動畫

See the Pen SVG Animate Along Path by Eddie Lin (@yshlin) on CodePen.

導引線動畫的效果就是讓圖形可以沿著指定的路徑移動,要達到這個效果首先需要一條繪製好的路徑,要注意的是導引線路徑只支援 

path
元素,所以如果發現匯出的 SVG 內容出現 
polyline
或是其它元素,那麼可能要想辦法在向量圖軟體中進行轉換(可以試著切斷封閉路徑、或接上一小段曲線路徑再匯出)。假設我們已經把 
path
元素準備好,並設定
id="wire"
<path d="M-1,1.499h83c0,0,10.5,1,10.5,11.25s-10.25,12.25-10.25,12.25h-43.5c0,0-10.75,0.75-11.75,11.5s9.75,12.25,9.75,12.25H343.5"
      stroke="none" fill="none" id="wire"></path>

而我們想讓一個 

circle
元素沿著此路徑移動,只需要在 
circle
元素內加上一個 
animateMotion
子元素如下:
<circle cx="" cy="" r="5" fill="#529fd9">
  <animateMotion dur="1.6s" repeatCount="indefinite">
    <mpath xlink:href="#wire"></mpath>
  </animateMotion>
</circle>

其中 

dur
和 
repeatCount
屬性就和前述 
animate
元素的屬性作用相同,接著在 
animateMotion
元素內再加上一個 
mpath
子元素,並設定屬性值
xlink:href="#wire"
,透過 
path
的 id 
#wire
來指定行進路徑。詳細說明文件請見 MDN

環形進度列

See the Pen SVG Progress Circle by Eddie Lin (@yshlin) on CodePen.

這個範例中將示範 SVG 如何搭配 Javascript 來做動畫,實際的應用在這次十週年活動的問卷調查結果中,往下捲動可以看到環形的進度列逐漸填滿至指定的百分比。首先要準備的 SVG 很簡單,只需要兩個圓圈,內容如下:

<circle class="gauge" cx="200" cy="200" r="100"></circle>
  <circle stroke-dasharray="629" stroke-dashoffset="629" class="arc" cx="200" cy="200" r="100" data-to="88"></circle>

第一行的 

circle
是靜態的灰色底,而第二行的 
circle
元素則是我們要用 Javascript 來控制增長的進度列。這邊一樣運用前述線條繪製動畫的作法,設定
stroke-dasharray
stroke-dashoffset
 ,而線條的總長度即圓周的長度 2r * pi,範例中 r 值(即半徑)為 100,因此圓周總長為 628.31,為了避免填滿時出現細縫,採用無條件進位,也就是 629,我們先把這個數值設定在
stroke-dasharray
stroke-dashoffset
屬性中,讓進度列一開始即為歸零的狀態。接著把目標的進度值(百分比)放在進度列元素的 
data-to
自訂屬性中,以便 Javascript 可以直接從元素中取得目標值,不用針對每個進度列個別設定在 Javascript 之中。

接下來我們針對進度列的 circle 元素設定漸變:

.arc {
  .transition(stroke-dashoffset 1s linear);
}

詳細的樣式設定就不在此贅述,可直接參考上面的 Codepen。這邊透過 CSS 設定了 

.arc
元素的 
stroke-dashoffset
屬性,使數值在改變時進行 1 秒鐘的線性漸變。這樣一來我們就不必使用 Javascript 去運算補齊中間的漸變值。

最後就是透過 Javascript 將 

stroke-dashoffset
依照比例設定為目標值,範例中的重點在這段程式碼:
var toOffset = (100 - to) * fullOffset / 100;
$('#go').click(function() {
  progressCircle.attr('stroke-dashoffset', toOffset);
});

先算出 

stroke-dashoffset
  的目標值
toOffset
,接著在按鈕 
#go
  按下時改變 
stroke-dashoffset
的值,以啟動前面在 CSS 中設定的漸變動畫。

若想支援一口氣啟動多個不同進度的進度列,可以參考活動網頁 Github 的 JQuery Plugin 寫法

另外問卷統計結果頁中還使用了 JQuery Waypoints 套件以在捲動至進度列時驅動它,用法詳見此活動網頁 Github

偵測相容性

關於 SVG 以及 SVG SMIL 動畫(註 1)的瀏覽器相容性,在 27 期電子報有介紹過的 caniuse.com 網站中可以查詢得到,詳見這兩個連結-Can I use SVG? Can I use SVG SMIL animation?

那麼問題是,SVG SMIL 動畫 和透過 CSS、Javascript 設定的動畫,支援度有沒有不同呢?經測試 IE9 以上有支援靜態 SVG,但目前完全不支援 SMIL,透過 CSS 設定的 animation、transition 也沒有任何作用,但透過 Javascript 進行的漸變則可以正常運作。而支援 SMIL 的瀏覽器版本如 Firefox、Chrome、Safari、Opera 等則同時也支援 CSS、Javascript 漸變動畫。

因此若要最大化相容性可考慮使用 Javascript 來進行動畫的設定(註 2)。若只想簡單地使用 SMIL 或 CSS 來設定動畫,在不支援 SVG 動畫時降級為靜態圖片或 GIF 等等,則可透過

Modernizr.svg
 、
Modernizr.smil
來偵測瀏覽器支援(註 3)。

Firefox 十週年系列活動

最後,如果你還沒聽過 Firefox 十週年系列活動,別忘了參加幸福的狐家元件線上活動轉輪抽大獎,並領票參加 11/22 ~ 11/23 在華山文創園區舉辦的網路自由日展覽活動哦!

附註

  • 註 1:SVG SMIL (Synchronized Multimedia Integration Language) 泛指以 XML 形式在 SVG 圖形內設定的動畫,除了前面用到過的 animate、animateMotion 元素以外還有 animateTransform 等,詳見 MDN 說明文件
  • 註 2:除了簡單的數值型漸變外,若要用 Javascript 實作依循時間函式的數值型漸變或是路徑形狀的漸變,可透過額外的 Javascript 套件如 Snap.svg
  • 註 3:在 Modernizr 網站上可自訂想偵測的功能,產生對應的 Javascript 腳本並嵌入你的網頁。讓你在開發時可以偵測瀏覽器的支援度以執行對應的 CSS/Javascript。

0 則回應

js13kGames:遊戲開發專屬的 Code Golf js13kGames:遊戲開發專屬的 Code Golf 1 年前
用 JavaScript 錯誤訊息協助 Web 開發者 用 JavaScript 錯誤訊息協助 Web 開發者 1 年前
運用 Web 技術打造電視遙控器的操作環境 運用 Web 技術打造電視遙控器的操作環境 1 年前
在 Firefox 上建構 WebVR 在 Firefox 上建構 WebVR 1 年前
免 Flash 實作的 Web 剪貼簿 免 Flash 實作的 Web 剪貼簿 2 年前
淺談 W3C Sensor API的發展 淺談 W3C Sensor API的發展 2 年前

熱門文章

最新消息