
SVG 動畫的歷史:SMIL 語法與侷限
回溯網頁向量圖形動畫的早期年代,有一種方式能在不依賴 JavaScript 或 CSS 的情況下,直接在 SVG 內賦予圖形「生命」。這項技術就是 SMIL 動畫 — 全名 Synchronized Multimedia Integration Language,原本是一種描述多媒體同步的 XML 語言,被引入 SVG 用於定義動畫效果。透過編寫 SVG 標籤中的 <animate>、<animateTransform> 等元素,開發者可以宣告式地描述圖形如何隨時間變化,例如位置移動、顏色變換或縮放旋轉。SMIL 讓靜態的 SVG 插圖猶如自帶劇本,在載入頁面後就能自動地一幕幕演出。
SMIL 的語法相當直覺:你只需在 SVG 元素內嵌入對應的動畫子元素,指定目標屬性和時間參數即可。以下是一個簡單範例,我們讓一個橘色矩形來回水平移動:
<svg width="200" height="100">
<rect x="50" y="20" width="60" height="60" fill="orange">
<animate attributeName="x" values="50;100;50"
dur="4s" repeatCount="indefinite" />
</rect>
</svg>
在這段程式碼中,<animate> 標籤定義了動畫行為:attributeName="x" 表示我們要改變矩形的 x 座標,values="50;100;50" 則設定了動畫經過的一系列數值(從 x=50 到 x=100 再回到 x=50),dur="4s" 指定動畫週期為 4 秒,repeatCount="indefinite" 則讓動畫無限循環。因此,當 SVG 載入後,我們會看到矩形以 4 秒為一個來回週期,不斷在水平軸上往返移動。
SMIL 作為 SVG 動畫的開端,展現了強大的描述力和便利性:不需撰寫任何外部程式碼,即可實現許多精美的動態效果。然而,它也存在明顯的侷限。首先,瀏覽器相容性問題曾經嚴重限制了 SMIL 的普及——並非所有主流瀏覽器都完整支援這套 SVG 動畫規範,特別是早期的 Internet Explorer 和舊版 Edge 根本不支援 SMIL,而其他瀏覽器的實作細節也不盡相同。其次,互動控制的彈性不足:由於 SMIL 採用純宣告式定義,動畫流程一旦設定就自行運行,開發者難以在執行途中動態改變效果或根據使用者行為加以控制(雖然可以透過 DOM 介面如 beginElement() 啟動或停止 SMIL 動畫,但操作範圍遠不及直接使用 JavaScript 來得靈活)。再者,規範發展停滯:SMIL 動畫在 2000 年代隨 SVG 1.1 標準問世後,後續規範更新緩慢,2008 年之後幾乎沒有新功能加入,難以跟上現代網頁開發對複雜動畫日益增長的需求。
上述因素導致 SMIL 在後來逐漸淡出主流舞台。隨著網頁技術的演進,業界開始尋找替代方案,這也為下一階段——採用 CSS 和 JavaScript 來操控 SVG 動畫——埋下了伏筆。
SMIL 被捨棄的原因與過渡至 CSS/JavaScript
SMIL 雖然曾經風光,但隨著時間推移,幾個關鍵因素導致它的地位被後來的技術所取代。首先是生態支援的問題:由於 SMIL 並未在所有瀏覽器上獲得完善實作,開發者在不同平臺間面臨相容性挑戰,這使大家對採用 SMIL 心存疑慮。特別是微軟的 IE/Edge 瀏覽器長期缺席 SMIL 支援,而 WebKit 引擎對 SMIL 的投入也有限。其次,瀏覽器研發團隊在 2010 年代中期開始重新審視網頁動畫的統一模型。Google Chrome 團隊一度在 2015 年公開表態計畫廢除 SMIL,轉而專注推廣更通用的動畫方案(如 CSS 動畫和 Web Animations API)。儘管隨後因社群反對而暫緩了該計畫,但這反映了業界的一種趨勢:偏好使用更通用、更具整合性的技術來實現動畫效果。透過 CSS 或 JavaScript,我們可以用一套方法同時操控 HTML 和 SVG 元素的動畫,不再局限於 SVG 特有的語法,對開發者而言無疑更加統一方便。
最後,替代方案的成熟讓 SMIL 顯得沒那麼不可或缺。CSS3 引入的動畫功能(如 @keyframes 和 transition)使我們能對 SVG 元素應用許多炫目的效果;同時 JavaScript 生態的蓬勃(含原生 Web Animations API 的誕生),也提供了以程式碼操縱動畫的強大選項。另外,一批優秀的 JS 動畫函式庫(例如 GSAP、Snap.svg 等)的崛起,進一步降低了前端實現複雜 SVG 動畫的門檻。與此同時,SMIL 的開發活躍度卻每況愈下,其大部分功能在實務中已能被上述新工具所覆蓋。綜合這些因素,業界逐漸默契地將重心從 SMIL 轉移開來,新專案更傾向使用 CSS 和 JS 來實作動畫,SMIL 則淪為歷史遺產般的存在。
需要強調的是,即便 SMIL 在趨勢上被「捨棄」,某些它擅長的獨特功能直到最近才在其他方案中找到對策。例如,沿著任意路徑移動物件這件事,純 CSS 長期以來無法輕易達成(雖然 CSS 近年加入了 Motion Path 模組,可讓元素沿自訂路徑移動,但用法較複雜且瀏覽器支援有限);又或者像漸層顏色的動畫效果、兩種複雜形狀之間的平滑過渡(morphing),這些都是 SMIL 原生支援但 CSS 一直缺席的領域。過去要實現這類特殊效果,開發者只能求助於 JavaScript 或第三方工具。因此,SMIL 的離場某種程度上也推動了瀏覽器引入更新能力來填補這些空白。我們如今處於以 CSS 和 JS 為主角的動畫時代,其實正是站在 SMIL 奠定的基礎上繼續演化。
經過以上轉變,SVG 動畫的主角已換成 CSS 和 JavaScript。接下來,我們先來看看 CSS 如何運用在 SVG 動畫上。
CSS 在 SVG 動畫中的應用:關鍵影格、過渡與捲動動畫
CSS 成為了現代前端控制 SVG 動態效果的有力武器。只要將 SVG 內聯到 HTML,我們便能像操控一般元素一樣,用 CSS 選擇器鎖定其中的圖形並添加動畫。典型方法包括:使用 @keyframes 定義關鍵影格動畫(描述狀態隨時間的漸變),以及利用 transition 定義狀態改變時的平滑過渡。由於許多 SVG 屬性(如填色 fill、描邊 stroke、變形 transform 等)都能被 CSS 控制,我們可以輕鬆為圖形設計各種效果,例如頁面載入時淡入、滑鼠懸停時放大等,完全不需藉助 JS 即可實現。
讓我們看一個簡單的例子:用 CSS 控制 SVG 圖形的旋轉動畫。假設有一個 SVG 正方形,希望它不停自轉,這可以用 CSS 關鍵影格動畫達成:
<svg width="100" height="100">
<rect id="square" x="40" y="40" width="20" height="20" fill="green" />
</svg>
<style>
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
#square {
transform-origin: 50% 50%;
animation: spin 2s linear infinite;
}
</style>
上述程式碼定義了一個名為 spin 的動畫,從 0 度旋轉到 360 度。接著在選擇器 #square 上套用此動畫,並將旋轉中心 (transform-origin) 設為圖形自身的中心點(50% 50% 表示元素範圍的正中央)。如此一來,綠色方塊就會持續繞著自己的中心旋轉。整個效果完全由 CSS 達成,瀏覽器會利用最佳化的渲染管線(例如 GPU 加速)確保動畫順暢流暢。
除了使用 @keyframes,CSS 的過渡 (transition) 也經常應用在 SVG 動畫中。過渡效果的典型用法是當 SVG 元素的某個屬性發生變化時,自動插入一段平滑的改變過程。例如,可以設定一個圖標在滑鼠 hover 時改變顏色或大小,透過 CSS transition: 0.3s 來讓這些變化以 0.3 秒漸進呈現,而非瞬間跳變。相較於手動用 JS 逐步修改屬性值,CSS 過渡在簡單互動場景下更為簡潔高效。
此外,現代 CSS 也開始支援隨卷動變化的動畫。透過新的 Scroll-driven Animations 規範,我們可以將動畫時間軸直接綁定到頁面滾動位置(例如使用 @scroll-timeline),純 CSS 即可實現過去需要監聽卷軸事件才能達成的視差滾動、閱讀進度條等效果。儘管這項功能目前僅在部分瀏覽器中提供,隨時間推進將有望成為標準工具,屆時前端開發者可以更方便地打造出豐富的卷動交互動畫。
當然,CSS 並非萬能。若動畫需要更複雜的邏輯或精細控制,便要請出 JavaScript 登場。接下來,我們看看如何以原生 JS 深入掌控 SVG 動畫。
原生 JavaScript 實作 SVG 動畫:requestAnimationFrame、路徑長度與捲動觸發
當動畫需求超出 CSS 能力範圍時,就該由 JavaScript 粉墨登場。利用 requestAnimationFrame 這類 API,我們可以每一畫面幀動態更新 SVG 元素屬性,按照自定的邏輯打造出更多變、互動性更強的效果。例如,要在 SVG 路徑上實現手寫繪製效果,可以利用 <path> 元素的 getTotalLength() 來取得路徑總長度,接著動態調整其 stroke-dashoffset,讓線條按預定速度從起點「畫」到終點。
JavaScript 也能配合滾動事件來觸發動畫。其中使用 IntersectionObserver API 可輕鬆監測元素何時進入視窗(viewport),讓我們可以在 SVG 元素真的出現在用戶視野時才啟動動畫,未出現時則不執行,從而提升效能和用戶體驗。
以下範例展示了如何將 requestAnimationFrame 與 IntersectionObserver 結合,實現一個捲動到指定 SVG 時才啟動的路徑繪製動畫。我們有一條紅色曲線,其起始狀態被設定為隱藏(利用 stroke-dasharray 和 stroke-dashoffset),當曲線滾動進入視窗後,JavaScript 會逐步將 dashoffset 從全長減至 0,產生線條從無到有被「畫」出來的效果:
<svg width="100" height="100">
<path id="path" d="M10 50 C 30 10, 70 90, 90 50"
stroke="red" fill="none" stroke-width="3" />
</svg>
<script>
const path = document.getElementById('path');
const pathLength = path.getTotalLength();
path.style.strokeDasharray = pathLength;
path.style.strokeDashoffset = pathLength;
const duration = 2000; // 繪製持續 2 秒
let start = null;
function draw(timestamp) {
if (!start) start = timestamp;
const progress = Math.min((timestamp - start) / duration, 1);
path.style.strokeDashoffset = pathLength * (1 - progress);
if (progress < 1) {
requestAnimationFrame(draw);
}
}
const observer = new IntersectionObserver(entries => {
if (entries[0].isIntersecting) {
requestAnimationFrame(draw);
observer.unobserve(path);
}
});
observer.observe(path);
</script>
上述程式碼中,我們先取得 <path> 元素的總長度,並將 stroke-dasharray 和 stroke-dashoffset 設為該值,讓線條起始時完全不可見。然後定義 draw() 函式,使用 requestAnimationFrame 每幀遞減 dashoffset(根據時間計算進度 progress 從 0 到 1),逐步將紅線繪出。最後藉由 IntersectionObserver 監測路徑元素何時進入視窗,一旦出現即觸發動畫並取消監視,確保動畫只執行一次且在需要時才運行,避免資源浪費。
總結
回顧整個 SVG 動畫的演進歷程,我們見證了從 SMIL 到 CSS、再到 JavaScript 的技術更迭。各種方法各有其適用之處:SMIL 作為開路先鋒為 SVG 賦予了生命力,CSS 以簡潔的語法接棒覆蓋了大部分常見動畫,而 JavaScript 則憑藉無與倫比的靈活性將不可能化為可能。前端開發者唯有通曉這些工具的優劣與適用場景,才能在實際專案中揮灑自如,打造出令人驚嘆的 SVG 動態體驗。