chromium 手下留情,別廢掉 SVG SMIL 呀!
前幾個禮拜看到 jotai 的作者推文提到 jotai 的 logo 用了 animateTransform
來製作動畫(不知道 jotai 的可以看這篇 - Jōtai 介紹):
If you are not familiar with SVG animation, check this out. jotai https://t.co/Zadia8oug7 👻 logo is using <animateTransform>. pic.twitter.com/vll2SU5f16
— Daishi Kato (@dai_shi) June 6, 2021
(要等一會兒才會出現...如果沒有,可能是 cache 問題,可以點此開新頁查看。)
svg 檔案會動不稀奇,像是十分鐘、五步驟,SVG 動起來!中介紹的,透過第三方套件可以很容易達成,或是用一些繪圖、動畫工具也行,例如:SVGator,不過這些都得搭配 JavaScript,然而簡單的動畫只要利用 SVG 支援的語法即可達成!
今天就一起來了解一下,怎麼在不靠其它套件的狀況下,單純的製作 SVG 動畫。
能讓 SVG 不靠 JavaScript 與 CSS 就能動起來是因為使用了 SMIL(Synchronized Multimedia Integration Language),音同 smile,是 W3C 的標準之一,旨在以 XML 格式提供多媒體的交互表現(白話點其實就是動畫),是 Web 上動畫的開路先鋒,啟發了 Web animation 與 CSS animation。SVG 與 SMIL 的開發團隊合作,讓 SVG 能利用 SMIL 達到如下效果:
offset-path
光是這些特性就夠我們組合出很多種的動畫了,還不需要 JavaScript 與 CSS 的輔助。
使用方法也不難,只要在 SVG 元素內置入以下四種元素即可操作動畫:
<set>
<animate>
<animateTransform>
<animateMotion>
接著我們針對這四種元素一一介紹。
不過在開始前,有個小插曲值得一提。
SMIL 這個標準其實在 2015 年的時候差點被廢除,因為使用人數少,支援的主流瀏覽器也不多,所以 chromium 工程師本來想要 deprecate 它,順勢推廣 Web animation 與 CSS animation,但在消息公布後,收到了社群很多人的回饋,最終決定暫停廢除計劃,也因此我們到現在都還能使用 SVG SMIL animation,主流瀏覽器也都支援了,使用者也從 0.04% 上升到 1% 以上。
有興趣的讀者可以回追一下這串討論。
利用 <set>
元素你能夠指定在一段時間後,改變 svg 的一個屬性,例如 2 秒後將 Rick 的眼睛變成往下看:
疑?你說他本來就是往下看的?
那是因為 set
不會重複執行,從你載入這篇文章到看到這個位置為止,相信已經超過 2 秒,所以已經是執行後的結果,建議你按右鍵 -> "在新分頁中開啟圖片",實際體驗一下,再不然看看下面的 gif 也行:
相關程式碼如下:
<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>
元素讓你能針對單一屬性變化套用動畫補間效果。用法一樣是放在你想要套用效果的 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
,讓 Rick 的眼睛向右看。
也可以用來改變顏色:
也因為可以用來改變顏色,所以本來有個 <animateColor>
元素就被取代掉了,現在已經 deprecated 了。
<animateTransform>
可以用來控制 transform
屬性,用 animate
無法做到。跟 CSS 中的 transform 一樣,可以控制 translation、scaling、rotation 跟 skewing。
可以讓 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 的類型是什麼,rotate
、scale
都可以。其餘 from
、to
、begin
、dur
等參數都與之前的相同,用來指定動畫的起始終點值、時間長度與執行次數。
最後一個元素,animateMotion
,讓 svg 沿著軌跡 path
移動(若看不到效果請以分頁開新圖片):
<!--軌跡-->
<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 物件,可以設定為 auto
或 auto-reverse
:
<animateMotion
path="M10,50 q60,50 100,0 q60,-50 100,0"
begin="0s" dur="10s" repeatCount="indefinite" rotate="auto"
/>
此外,除了給定 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 內,可以透過 id
與 xlink: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):
(註:經實測,animateTransform 在手機上似乎不支援,請用桌面版瀏覽器查看此範例)
在上面的 Demo 裡面,我們可以發現 SVG animate element 有很多參數可以使用,範例中只用到了一部分,但其實這些參數能設定的值都有不少變化,想要清楚知道每一個參數的用途與範例,推薦參考這篇文章 - A Guide to SVG Animations (SMIL),不介意簡體中文的話,可以看這篇,都寫得非常好非常詳細。
在這個章節我會重點介紹一些我覺得比較有趣及實用的參數。
from
跟 to
在前面的範例中都有看到,功能也如同字面般好懂,就是指定動畫變化的移動區間,從(from
)某個值變化到(to
)另個值;而 by
則是代表位移量,相對於明確告知要變動到哪個值,我們可以用 by
告訴 svg 要變動”多少的量“,例如前面 animateTransform
的例子,我們可以改為:
<animateTransform attributeName="transform"
type="rotate"
from="0 0 0" by="360"
begin="0s" dur="10s"
repeatCount="indefinite"
/>
看到這邊你應該會注意到,by
跟 to
功能上有點重複,所以彼此之間有優先權,如果同時有指定 to
與 by
,則只會套用到 to
的值。
再來看看 values
,這個剛剛的範例都沒出現,它的功用是來補足 from
、to
、by
的不足。不足的點在於, from
、to
、by
只能指定兩個值之間的變化,從 a 變化到 b,而 values
可以給定多個值,用分號 ;
隔開,就能有 a -> b -> c -> b -> a 這樣的變化,舉個例子:
<animateTransform attributeName="transform"
type="translate"
values="20;120;20"
begin="0s" dur="3s"
repeatCount="indefinite"
/>
begin
跟 end
分別用來控制何時開始執行動畫,何時停止動畫,在上面的例子中我們都只用到時間,像是 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 頭的動畫等到太空船的動畫做完後再啟動,效果如下(這需要麻煩讀者用新分頁開啟圖片來觀看):
另一個我覺得實用的值是 event-value
,看名字就知道,是可以依照 event
來啟動或終結動畫,用法與 syncbase-value
雷同,給定元素 id,然後根據該元素觸發的事件讓動畫 begin
或是 end
。幾乎所有 DOM element 支援的 event 都能使用,MDN 有列出所有可用事件。
一樣舉個例子,點擊 Rick 就能讓太空船移動(礙於 blog 格式,這範例得放在 codepen 上):
See the Pen SVG SMIL Demo - rick and spaceship by Arvin (@arvin0731) on 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]"
來啟動。
See the Pen SVG SMIL Demo - rick and spaceship - event trigger by Arvin (@arvin0731) on CodePen.
這三個參數主要讓你能夠更細微的調整動畫的速度變化。
calcMode
有四種模式:discrete
、linear
、paced
、spline
。
discrete 顧名思義就是離散的,from
值跳到 to
值不做補間; linear 跟 paced 我覺得效果雷同,都是讓讓補間動畫的速度維持一致(linear)與平均(paced); spline
則是使用貝式曲線,需要搭配 keyTimes
與 keySplines
來使用。
keyTimes
就是關鍵影格,跟前面提過的 values
一樣,可以接受多個以分號區隔的值,定義動畫的關鍵時間點,搭配不同的 calcMode
就能在不同的時間點有不同的速度效果。
keySpline
是當你 calcMode
設定為 spline
時,用來定義貝式曲線的四個控制點的。
這邊直接引用對岸網友精緻的 Demo 頁面給大家參考,用看的會比較清楚好理解:calcMode、keyTimes、keySplines 屬性 DEMO。該作者對於 keySpline
的值也有畫圖說明,很好懂,有興趣研究的話值得一看。
看到最後,不知道你會不會有個疑問:如果我想針對同的 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"
/>
(註:經實測,animateTransform 在手機上似乎不支援,請用桌面版瀏覽器查看此範例)
今天花了不小的篇幅介紹了 SVG SMIL animation,感謝看到這邊的各位,製作 Demo 的過程對我來說很有趣,也學習了怎麼繪製 SVG,從網路上的其他資源也查到許多詳細的資料,收穫不少!希望對看到這篇文章的你們也能有所啟發,除了常用的 Web animation 與 CSS animation 外,有機會也試試用 SVG 直接作動畫吧!