10 分鐘 svg 動畫 (2) — 簽名動畫
本篇預計產出之結果:
類似的手法在許多網站上都可以看到,像是用在 hero image 上的 slogan、流程圖進度條動畫(給大家看一個自己很喜歡的網頁),而 vivus.js 也是用同樣的原理作出來的 animation library。
而這原理說穿了,就只是對兩個 svg 的屬性動手腳而已: stroke-dasharray
和 stroke-dashoffset
,兩者的作用分別為:
而我們只需先將 stroke-dasharray
設成和圖形的長度相同,生成出一段長度相同的「大間隔」,再將 stroke-dashoffset
也設成和圖形的長度相同,將實線的部分推至「後方」看不見的地方,最後再將 stroke-dashoffset
慢慢減少到 0 ,使得實線部分被擠出來,就會有簽名的效果了。
這部分可以看這個網站的詳細講說
那麼讓我們開始吧!
首先我們要先有一張線條圖的向量檔( .svg),這個例子的字是用 Illustrator 的鋼筆拉出來的,不過由於我的技術還在超新手等級(應該看得出來吧 XD),所以就不獻醜說明怎麼拉的了。
存檔時選擇 SVG 格式
接下來將剛存好的 .svg 檔用文字編輯器打開,會得到一串 svg 的程式碼
由於本例中的字是由兩次鋼筆完成的(先寫 ‘R’ 的直豎,然後再一筆寫完剩餘部份),所以我們可以看到有兩個 <path>
,將這兩個 <path>
複製到我們接下來要工作的 html 檔裡的 <svg></svg>
裡面(上面那一大堆參數看起來很討厭),並設定好 width
和 height
,以及 class
及 id
(依個人喜好設定即可),現在的 html 會長這樣:
<svg width="500" height="500" id="sign">
<path class="path" d="M79.5,143.5c0,0,22,223-33,238"/>
<path class="path" d="M74.5,153.5c0,0,124-44,129-5s0,76-21,91s-79,37-89,23s7-19,10-13s47,185,85,140s22-82,23-88s27,148,64,68s-64-73-70-66s102,30,121,5c-19,25-70,82,0,73s91-202,90-232s-39-31-32,12s24,226,32,220c-1-42-26-97,36-88c-33,25-73,59,9,76"/>
</svg>
好的,現在用瀏覽器打開應該會發現一張你不認識的圖,我們必需要再加上 一些 css:
.path{
fill: none;
stroke: #000;
stroke-width: 3;
}
接下來就是 javascript 的事了,以下會使用到一些 ES6 的寫法,不熟悉者可以自行至 babel 轉為 ES5。我們先取得 DOM 元素的控制權
let svg = document.querySelector('svg#sign');
let pathes = svg.querySelectorAll('.path');
由於我們有兩個 <path>
,所以此時 pathes
會是一個長度為 2 的陣列。接下來對 pathes
做一連串的設定:
let lengths = [];
let speed = 500;pathes.forEach((el, i) => {
let len = el.getTotalLength();
lengths.push(len); el.setAttribute('stroke-dasharray', len);
el.setAttribute('stroke-dashoffset', len); setTimeout(()=>{
el.style.transition = `stroke-dashoffset ${len/speed}s linear`;
el.addEventListener('transitionend', () => { draw(i+1) });
}, 1);
});
迴圈的一開始我們先用 getTotalLength
取得當前 <path>
的長度,並 push 至紀錄長度的陣列 lengths
,如果是其他像 <circle>
、 <rect>
等元素,則可透過簡單的公式自行計算出長度。
還記得我們最開始說的原理嗎?接下來便是將我們這次的兩個主角屬性的值設定成和圖形的長度一樣。
最後是設定 transition 的部分,接持續時間設成 len/speed
(speed 可依個人喜好做調整),並且在 transition 結束時接著執行下一個 <path>
的 transition(下面會定義 function)。這裡會用到 setTimeout 原因是不這麼做的話,網頁載入後因為 stroke-dashoffset
的值有變化,你會看到你的動畫正在倒轉(還沒準備好就開始的感覺 XD),就算我用 Promise 寫還是一樣,只好出此下策,知道更好作法的大大們歡迎告知!
接下來定義我們動畫的 function,做的事情只有一件:將該元素的 stroke-dashoffset
改為 0 ,並藉著前面設定好的 transition 來完成動畫
function draw(i){
if(i >= pathes.length){
return;
}
pathes[i].setAttribute('stroke-dashoffset', 0);
}
最後加上按紐或其他方法來觸發 function,就大功告成了!
附上完整程式碼的 codepen: