讓 SVG 自己動起來! SVG SMIL Animation 介紹


Posted by ArvinH on 2021-06-21

前言

前幾個禮拜看到 jotai 的作者推文提到 jotai 的 logo 用了 animateTransform 來製作動畫(不知道 jotai 的可以看這篇 - Jōtai 介紹):

jotai logo

(要等一會兒才會出現...如果沒有,可能是 cache 問題,可以點開新頁查看。)

svg 檔案會動不稀奇,像是十分鐘、五步驟,SVG 動起來!中介紹的,透過第三方套件可以很容易達成,或是用一些繪圖、動畫工具也行,例如:SVGator,不過這些都得搭配 JavaScript,然而簡單的動畫只要利用 SVG 支援的語法即可達成!

今天就一起來了解一下,怎麼在不靠其它套件的狀況下,單純的製作 SVG 動畫。

SVG animation with SMIL

能讓 SVG 不靠 JavaScript 與 CSS 就能動起來是因為使用了 SMIL(Synchronized Multimedia Integration Language),音同 smile,是 W3C 的標準之一,旨在以 XML 格式提供多媒體的交互表現(白話點其實就是動畫),是 Web 上動畫的開路先鋒,啟發了 Web animation 與 CSS animation。SVG 與 SMIL 的開發團隊合作,讓 SVG 能利用 SMIL 達到如下效果:

  • 動畫化元素的數值屬性(x, y 值等等)
  • 動畫化元素的 transform 屬性(平移、旋轉)
  • 動畫化元素顏色
  • 軌跡路線移動動畫,類似於 CSS 中的 offset-path

光是這些特性就夠我們組合出很多種的動畫了,還不需要 JavaScript 與 CSS 的輔助。

使用方法也不難,只要在 SVG 元素內置入以下四種元素即可操作動畫:

  1. <set>
  2. <animate>
  3. <animateTransform>
  4. <animateMotion>

接著我們針對這四種元素一一介紹。

不過在開始前,有個小插曲值得一提。

SMIL 這個標準其實在 2015 年的時候差點被廢除,因為使用人數少,支援的主流瀏覽器也不多,所以 chromium 工程師本來想要 deprecate 它,順勢推廣 Web animation 與 CSS animation,但在消息公布後,收到了社群很多人的回饋,最終決定暫停廢除計劃,也因此我們到現在都還能使用 SVG SMIL animation,主流瀏覽器也都支援了,使用者也從 0.04% 上升到 1% 以上

有興趣的讀者可以回追一下這串討論

SVG animation element 介紹與示範

\<set>

利用 <set> 元素你能夠指定在一段時間後,改變 svg 的一個屬性,例如 2 秒後將 Rick 的眼睛變成往下看:

set element demo

疑?你說他本來就是往下看的?

那是因為 set 不會重複執行,從你載入這篇文章到看到這個位置為止,相信已經超過 2 秒,所以已經是執行後的結果,建議你按右鍵 -> "在新分頁中開啟圖片",實際體驗一下,再不然看看下面的 gif 也行:

set element demo

相關程式碼如下:

<circle cx="56.7573" cy="92.8179" r="2" fill="black" stroke="black" stroke-width="1">
  <set attributeName="cy" to="105.7318" begin="2s" />
</circle>

<set> 元素放在你想要套用效果的 svg shape 內即可。

attributeName 指定你要更動的屬性;to 代表變化值;begin 代表從載入後的什麼時候開始執行。

除了 attributeName 外,有另一個參數叫 attributeType,用來告訴瀏覽器你要動畫化的屬性值是屬於 XML(e.g. cy),還是 CSS(e.g. opacity),不指定的話,瀏覽器會自己猜。不過呢,這個參數也已經 deprecated 了,所以實際上我們不再需要它。

\<animate>

<animate> 元素讓你能針對單一屬性變化套用動畫補間效果。用法一樣是放在你想要套用效果的 svg shape 內:

<circle cx="56.7573" cy="92.8179" r="2" fill="black" stroke="black" stroke-width="1">
  <animate
      attributeName="cx" from="56.7573" to="64.7573"
      dur="2s" repeatCount="indefinite" />
</circle>

<set> 相比,多了 from 屬性來指定要從哪個值開始做變化,dur 指定動畫的執行時間,repeatCount 指定要重複幾次,這邊我們設定 indefinite 讓他無限重播(若看不到效果請以分頁開新圖片):

animate element demo

利用 animate,讓 Rick 的眼睛向右看。

也可以用來改變顏色:

animate element color demo

也因為可以用來改變顏色,所以本來有個 <animateColor> 元素就被取代掉了,現在已經 deprecated 了

\<animateTransform>

<animateTransform> 可以用來控制 transform 屬性,用 animate 無法做到。跟 CSS 中的 transform 一樣,可以控制 translationscalingrotationskewing

animateTransform element demo

可以讓 Rick 頭轉起來,看起來頗ㄎㄧㄤ LOL
(註:經實測,animateTransform 在手機上似乎不支援,請用桌面版瀏覽器查看此範例)

<animateTransform attributeName="transform"
  type="rotate"
  from="0 0 0" to="360 0 0"
  begin="0s" dur="10s"
  repeatCount="indefinite"
/>

如上面所述,要控制 transform 屬性,所以 attributeName="transform",接著 type 參數就看你想要 transform 的類型是什麼,rotatescale 都可以。其餘 fromtobegindur等參數都與之前的相同,用來指定動畫的起始終點值、時間長度與執行次數。

\<animateMotion>

最後一個元素,animateMotion,讓 svg 沿著軌跡 path 移動(若看不到效果請以分頁開新圖片):

animateMotion element demo

<!--軌跡-->
<path d="M10,50 q60,50 100,0 q60,-50 100,0" stroke="black" stroke-width="2" />

<g>
  <!-- Rick 飛船 svg-->
  <animateMotion
    path="M10,50 q60,50 100,0 q60,-50 100,0"
    begin="0s" dur="10s" repeatCount="indefinite"
  />
</g>

上述程式碼內的 <path> 只是為了讓大家看清楚路徑與實際動畫的軌跡無關,實際使用上只要給定 animateMotion 一條 path 屬性值,包含 animateMotion 元素的 svg 就會跟著該路徑移動。

其他屬性值跟其他元素雷同,不過 animateMotion 還有個特別的屬性值 rotate,用來指定是否要隨著路徑移動的同時,選轉綁定的 svg 物件,可以設定為 autoauto-reverse

<animateMotion
  path="M10,50 q60,50 100,0 q60,-50 100,0"
  begin="0s" dur="10s" repeatCount="indefinite" rotate="auto"
/>

animateMotion element demo

此外,除了給定 path 屬性值外,其實也能夠利用既有的 <path> 來當作 animateMotion 的路徑,但是得透過 mpath 這個 sub-element:

<!--軌跡-->
<path id="path1" d="M10,50 q60,50 100,0 q60,-50 100,0" stroke="black" stroke-width="2" />

<g>
  <!-- Rick 飛船 svg-->
  <animateMotion begin="0s" dur="10s" repeatCount="indefinite">
    <mpath xlink:href="#path1" />
  </animateMotion>
</g>

要注意的是,若要使用 xlink:href 來指定連接的 svg 元素,在你的 <svg> tag 上得先記得宣告 xmlns:xlink="http://www.w3.org/1999/xlink"

<svg width="300" height="200" viewBox="0 0 500 300" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" >
</svg>

有了 xlink:href,我們也就不用像之前範例中所做的一樣,一定得把 animate 元素放在要綁定的 svg shape 內,可以透過 idxlink:href 來連結,例如第一個 <set> 的範例可改為:

<circle id="eyes" cx="56.7573" cy="92.8179" r="2" fill="black" stroke="black" stroke-width="1">
</circle>

<set xlink:href="#eyes" attributeName="cy" to="105.7318" begin="2s" />

至此我們介紹完了四種 SVG animation element,除了個別拿來使用外,這些元素是能夠組合在一起使用的,就只要個別把對應的 animate element 套用在想要的 svg shape 上即可,舉例來說,可以讓 Rick 旋轉的同時,髮色改變、眼睛轉動(可右鍵看 svg 原始碼,在裡面可以找到多個 animate element):

combine animate elements demo

(註:經實測,animateTransform 在手機上似乎不支援,請用桌面版瀏覽器查看此範例)

SVG SMIL animation 重點參數介紹

在上面的 Demo 裡面,我們可以發現 SVG animate element 有很多參數可以使用,範例中只用到了一部分,但其實這些參數能設定的值都有不少變化,想要清楚知道每一個參數的用途與範例,推薦參考這篇文章 - A Guide to SVG Animations (SMIL),不介意簡體中文的話,可以看這篇,都寫得非常好非常詳細。

在這個章節我會重點介紹一些我覺得比較有趣及實用的參數。

from, to, by, values

fromto 在前面的範例中都有看到,功能也如同字面般好懂,就是指定動畫變化的移動區間,從(from)某個值變化到(to)另個值;而 by 則是代表位移量,相對於明確告知要變動到哪個值,我們可以用 by 告訴 svg 要變動”多少的量“,例如前面 animateTransform 的例子,我們可以改為:

<animateTransform attributeName="transform"
  type="rotate"
  from="0 0 0" by="360"
  begin="0s" dur="10s"
  repeatCount="indefinite"
/>

看到這邊你應該會注意到,byto 功能上有點重複,所以彼此之間有優先權,如果同時有指定 toby,則只會套用到 to 的值。

再來看看 values,這個剛剛的範例都沒出現,它的功用是來補足 fromtoby 的不足。不足的點在於, fromtoby 只能指定兩個值之間的變化,從 a 變化到 b,而 values 可以給定多個值,用分號 ; 隔開,就能有 a -> b -> c -> b -> a 這樣的變化,舉個例子:

<animateTransform attributeName="transform"
  type="translate"
  values="20;120;20"
  begin="0s" dur="3s"
  repeatCount="indefinite"
/>

animate values attribute demo

begin, end

beginend 分別用來控制何時開始執行動畫,何時停止動畫,在上面的例子中我們都只用到時間,像是 begin="2s",但其實這兩個參數能給的值有非常多的種類,而且能向 values 一樣賦予多個值,只要用 ; 隔開即可:

begin = <offset-value> | <syncbase-value> | <event-value> | <repeat-value> | <accessKey-value> | <wallclock-sync-value>

每種類型的詳細介紹,我推薦直接看 MDN,或是對岸網友的整理,這邊我只說明幾個我覺得比較實用的。

首先是 <syncbase-value>

從字面有點難懂,主要是用其他 animate 元素的 begin/end 值再做加減,舉個例子就比較好懂:

<!-- Rick head -->
<g>
  <animateTransform attributeName="transform"
    type="scale"
    values="1;1.2;1"
    begin="ship.end" dur="3s"
    repeatCount="indefinite"
  />
</g>
<!-- spaceship -->
<g>
  <animateTransform id="ship" attributeName="transform"
    type="translate"
    values="20;120;20"
    begin="0s" dur="3s"
  />
</g>

這次範例中的 svg 內有兩個 animate 元素,給定針對太空船做動畫的元素一個 id 值 ship,然後在 Rick 的動畫元素上利用 begin="ship.end",就可以讓 Rick 頭的動畫等到太空船的動畫做完後再啟動,效果如下(這需要麻煩讀者用新分頁開啟圖片來觀看):

animate begin syncbase demo

另一個我覺得實用的值是 event-value,看名字就知道,是可以依照 event 來啟動或終結動畫,用法與 syncbase-value 雷同,給定元素 id,然後根據該元素觸發的事件讓動畫 begin 或是 end。幾乎所有 DOM element 支援的 event 都能使用,MDN 有列出所有可用事件。

一樣舉個例子,點擊 Rick 就能讓太空船移動(礙於 blog 格式,這範例得放在 codepen 上):

程式碼如下,rickhead.click[元素 id].[event]):

<g id="rickhead"> <!--// 略 --></g>
<g>
  <animateTransform id="ship" attributeName="transform"
    type="translate"
    values="20;120;20"
    begin="rickhead.click" dur="3s"
  />
</g>

最後是 indefinite,如果你的 begin 值為 indefinite,代表無限等待,這時就需要透過 [animate 元素].beginElement() 來觸發,或是用 <a> tag 的 xlink:href="#[animate 元素 id]" 來啟動。

calcMode, keyTimes, keySplines

這三個參數主要讓你能夠更細微的調整動畫的速度變化。

calcMode 有四種模式:discretelinearpacedspline

discrete 顧名思義就是離散的,from 值跳到 to 值不做補間; linearpaced 我覺得效果雷同,都是讓讓補間動畫的速度維持一致(linear)與平均(paced); spline 則是使用貝式曲線,需要搭配 keyTimeskeySplines 來使用。

keyTimes 就是關鍵影格,跟前面提過的 values 一樣,可以接受多個以分號區隔的值,定義動畫的關鍵時間點,搭配不同的 calcMode 就能在不同的時間點有不同的速度效果。

keySpline 是當你 calcMode 設定為 spline 時,用來定義貝式曲線的四個控制點的。

這邊直接引用對岸網友精緻的 Demo 頁面給大家參考,用看的會比較清楚好理解:calcMode、keyTimes、keySplines 屬性 DEMO。該作者對於 keySpline 的值也有畫圖說明,很好懂,有興趣研究的話值得一看。

additive

看到最後,不知道你會不會有個疑問:如果我想針對同的 SVG shape 的同個屬性做多個連續變化時該怎麼辦?

例如:透過 animateTransform 先將圖案放大再位移。

這時就要靠 additive 這個參數出馬了,additive 參數告知 SVG 是否要累加(sum)動畫效果,或是取代(replace),預設是 replace

例子:

<animateTransform attributeName="transform"
  type="scale"
  by="1.1"
  begin="0s" dur="5s"
  repeatCount="indefinite"
  additive="sum"
/>

<animateTransform attributeName="transform"
  type="rotate"
  from="0 0 0" to="360 0 0"
  begin="0s" dur="5s"
  repeatCount="indefinite"
  additive="sum"
/>

animate values attribute demo


(註:經實測,animateTransform 在手機上似乎不支援,請用桌面版瀏覽器查看此範例)

結論

今天花了不小的篇幅介紹了 SVG SMIL animation,感謝看到這邊的各位,製作 Demo 的過程對我來說很有趣,也學習了怎麼繪製 SVG,從網路上的其他資源也查到許多詳細的資料,收穫不少!希望對看到這篇文章的你們也能有所啟發,除了常用的 Web animation 與 CSS animation 外,有機會也試試用 SVG 直接作動畫吧!

資料來源

  1. 超级强大的SVG SMIL animation动画详解
  2. A Guide to SVG Animations (SMIL)
  3. SVG animation with SMIL
  4. SVG Animation

#Web #svg #animation #smil









Related Posts

[Day07] Monad

[Day07] Monad

Form with React 用 React 做一份表單

Form with React 用 React 做一份表單

CommonJS & ES module

CommonJS & ES module




Newsletter




Comments