
最近在處理 SVG 動畫時,遇到了一個有趣的畫面瑕疵。當我們使用 path 標籤搭配 marker-end 來加上箭頭時,瀏覽器展現動畫的行為跟我們預期的有點落差。
這篇文章來記錄這個問題的根本原因,以及如何透過簡單的技巧完美解決。
問題是甚麼
在網頁中,如果我們寫了 path 結合 marker-end 這樣的語法,本意是希望路徑在畫完的時候,終點才出現這個箭頭。
但是實際執行動畫時,只要路徑開始顯現,也就是透明度變成 1 的那一個瞬間,瀏覽器就會在終點立刻把這個箭頭渲染出來。
這會導致一個很不協調的現象:線條還在慢慢從起點畫過來,但終點已經提早立好了一個箭頭。
為什麼會這樣
這是因為 SVG 的 marker-end 本質上就是一個綁定在路徑端點上的裝飾元件。
當我們使用 stroke-dashoffset 來控制線條慢慢畫出來時,路徑只要一有顯示的跡象,瀏覽器就會認為端點已經存在,於是提前把裝飾的箭頭畫上去,沒有辦法隨著線條進度同步延遲出現。
解決方案
為了讓箭頭配合線條畫到的時間點才出現,我們需要分開處理路徑和箭頭。
第一步是移除 SVG 的 marker-end 屬性,這樣可以避免瀏覽器在路徑終點預先繪製箭頭。
第二步是獨立一個箭頭元件出來,這裡使用 polygon 標籤來當作獨立的箭頭,並且根據路徑終點的座標以及切線斜率來旋轉對齊端點。
程式碼實作
調整過後的程式碼像這樣:
HTML
<svg class="marathon-svg" viewBox="0 0 1400 260" xmlns="http://www.w3.org/2000/svg">
<!-- Track path -->
<path id="track" d="M80,150 Q280,40 480,120 Q680,240 880,140 Q1080,50 1280,150 L1360,180" stroke="#C8922A" stroke-width="6" stroke-linecap="round" fill="none" stroke-dasharray="1360" stroke-dashoffset="1360" style="
animation: dashDraw 3s ease forwards;
--dash-len: 1360;
animation-delay: calc(var(--scene-t,0s) * -1 + 9s);
animation-play-state: paused;
"></path>
<!-- Arrowhead that appears when drawing reaches the end (at 11.5s) -->
<polygon points="0 -9, 22 0, 0 9" fill="#C8922A" transform="translate(1360, 180) rotate(20.56)" style="
opacity: 0;
animation: fadeIn 0.3s ease forwards;
animation-delay: calc(var(--scene-t,0s) * -1 + 11.5s);
animation-play-state: paused;
"></polygon>
</svg>
透過把箭頭拉出來獨立控制,並且設定好對應的 animation-delay 讓它在時間點剛好對上線條畫完的一刻,就能解決箭頭提前出現的不協調感。
