SVG 在 image 標籤中的動態修改技巧
tag: Web; JavaScript; SVG; DOM; 動畫 SVG
最近在專案中遇到了「帶動畫 SVG 圖示」與 「image」標籤結合使用的場景,使用過程中發現水還是有點深,因此整理出來,供有相似場景的童鞋以參考。
問題背景
我們這裡有一個的帶動畫SVG 檔案
這是一個水波紋效果的 SVG,動畫時長是固定的,但是我們希望不同的標記動畫的播放時長可以略有不同,進而產生錯落交錯的感覺。期待效果如下:

其中控制動畫時長的屬性是寫死在 SVG 檔案中的(animate 標籤的 dur 屬性)。SVG 部分內容如下:
<circle cx="22" cy="22" r="6" stroke-opacity="0"> <animate attributeName="r" begin="1.5s" dur="3s" values="6;22" calcMode="linear" repeatCount="indefinite" /> <animate attributeName="stroke-opacity" begin="1.5s" dur="3s" values="1;0" calcMode="linear" repeatCount="indefinite" /> </circle> 複製程式碼
另外受限於元件要求,僅能使用 image
標籤進行載入 SVG。因此只能從 image.src 屬性作為突破入口。
解題思路
先確定基本思路:在 SVG 被插入到 image 標籤前,修改 SVG 檔案中動畫 animate 標籤的屬性 dur,以達到修改動畫時間的目的。
那麼我們先來看下 image 載入 SVG 檔案幾種的方式。
1. image 的 src 直接指定檔案路徑
<img src="./assets/rings.svg"> 複製程式碼
這種方式加載出來的 SVG 內容沒法被 JS 獲取到,更不要提修改屬性,因此該方案 放棄 。
2. 直接載入 base64 或者 Blob URL 字串
這種方式中,依靠的是 image 標籤支援 base64 和 Blob 型別 URL 的特性。
考慮可以先將 SVG 檔案轉換為對應的字串,然後通過正則表示式將動畫屬性修改,然後傳入 src 中來實現。不過這種方式需要編寫複雜的正則表示式,並且字串形式的 SVG 內容可讀性較差,因此這種方式也 不是最優的 。
如果可以先以 dom 形式修改 SVG 的動畫屬性,再將 dom 轉換為字串,最後再將字串轉換為 base64 或者 Blob 型別,就可以實現以上的需求了。
看起來這個思路靠譜,那麼就按照這個方向繼續探索。
處理過程
1. 獲取 dom
獲取 SVG 的方式有很多,但獲取方式不在本文重點,故只給出原生 JS 實現方式。
const xhr = new XMLHttpRequest(); xhr.addEventListener('load', () => { const resXML = xhr.responseXML; const svgDom = resXML.documentElement.cloneNode(true); }); xhr.open('GET', './rings.svg'); xhr.send(); 複製程式碼
2. 修改 dom
上文獲取的 svgDom 就是可操作的 dom 節點,接下來和操作 dom 一樣來操作它。
// 獲取 svg 中的 animate 標籤,使用 setAttribute 進行修改 dur、begin 等屬性,以下程式碼僅為示例 // 獲取 animation 節點 const ani = svgDom.children[0]; // 修改節點上的動畫時長 dur 屬性 ani.setAttribute('dur', Math.random() + 2 + 's'); 複製程式碼
3. 轉換 domString
接來下便需要將 dom 轉換成字串:通過 XMLSerializer
將 XML 轉換成 String 型別。
const svgStr = new XMLSerializer().serializeToString(svgDom); 複製程式碼
4. 轉換 URL
但是上面的 svgStr 是沒法直接傳給 image.src 的,需要將其轉換為 image 可以解析的 base64 或者 Blob URL 型別。
方案 A:
這裡我選擇了 Blob 型別進行轉換,先用 new Blob([svgStr])
轉換成 Blob 型別,再通過 URL.createObjectURL(blob)
方法將字串轉換成 Blob
型別 URL 傳入。
const blob = new Blob([svgStr], { type: 'image/svg+xml' }); const blobStr = URL.createObjectURL(blob); const template = `<img src="${blobStr}">`; // 最後插入模板 複製程式碼
方案 B:
當然除了使用 Blob 資料型別外,我們也可以使用 window.btoa()
方法將 svgStr 轉換為 base64 型別傳入。
const base64 = window.btoa(svgStr); const template = `<img src="data:image/svg+xml;base64,${base64}">` // 最後插入模板 複製程式碼
結論
通過「讀入 SVG -> 修改屬性 -> 轉換 URL -> 傳入 image」 這樣一個流程,最終實現了我們的預期。
以上思路希望對有類似場景的同學們有所啟發。 最後強調的是使用環境為 Chrome 瀏覽器,其他瀏覽器未做測試。