最近我們設計師反饋,他想要做如下的一個加載動畫。但是要麼效果好的導出的 GIF 體積特別大,看了下有 8M 多了,要麼體積小的 GIF 效果又特別不清楚。然後我看了下效果,發現其實用 SVG 動畫來實現應該比較簡單,於是就和設計師要了一下原始的稿子導出成 SVG 後處理了下。
將 AE 動效稿子轉成 SVG 動畫的話 Airbnb 有出過一款 Lottie 的工具。通過它的 AE 插件 Bodymovin 能夠以 JSON 的形式導出動畫信息和素材。然後在網頁上使用 bodymovin.js 動畫播放庫載入該 JSON 素材即可完成動效的轉換。具體的使用教程可以參考 Youtube 視頻《How to export an animation with Bodymovin》。
使用 Bodymovin 是真的非常方便,不過介於設計師需要的效果比較簡單,為了這個效果而每次去加載一個幾十KB的基礎庫和JSON文件實在是沒有必要。所以我這裡就基於 SVG + CSS 動畫來實現了下,最終的效果如下。最終體積也就 6KB,gzip 後會更小。
下面就來跟著我一塊一步步的實現它吧!這裡我不會特別詳細的描述每一步的基本原理,如果大家想了解 SVG 動畫的基礎知識的話可以先看看我之前寫的文章《SVG 動畫實踐1》。
動畫拆分分析通過觀察發現該動畫主要用到了平移、旋轉、透明度,寬度和顏色等屬性變化等動畫效果。這些都可以通過 CSS3 動畫來實現,剩下的我們需要對這些動畫進行拆分,先分別實現它們。最後將他們組合,通過一定的時間配合實現完整的效果。
在這裡我將該動效最終拆分成了以下幾個部分:
每一個單獨的動畫效果我們都需要對其進行處理,所以我們需要對導出的 SVG 進行元素的整理,將我們需要進行操作的元素進行分組標記。由於 Sketch 導出的 SVG 文件會帶有比較多的冗餘元素,所以我一般會在手工處理 SVG 之前在走一遍 svgo 這類工具對內容進行優化。這裡推薦張鑫旭老師寫的 SVG 在線壓縮合併工具,直接粘貼 SVG 代碼過去即可,非常簡單。下面是 SVG 整體結構的示意代碼。
<svg width="552px" height="552px" viewBox="0 0 552 552" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!--外圈-->
<g id="track-list">
<!--外圈1-->
<circle id="track-circle-1" />
<!--外圈2-->
<circle id="track-circle-2" />
<!--外圈3-->
<circle id="track-circle-3" />
</g>
<!--中間主體-->
<g id="main">
<!--主體綠色部分下-->
<g id="bottom-triangel">
<path d="..." />
</g>
<!--主體綠色部分上-->
<g id="top-triangel">
<path id="shadow" d="..." />
<path d="..." />
<!--主體白色圓球-->
<circle id="white-ball" />
</g>
<!--主體藍色部分-->
<g id="right-triangel">
<path d="..." />
<!--主體白色橫條-->
<rect id="white-line" />
</g>
</g>
<!--外圈藍球-->
<circle id="blue-ball" />
</svg>
可以看到我對 SVG 內的元素進行了重新的整理,將需要操作的元素都加上了 id 屬性,方便後續直接使用 CSS 選擇器選擇對象進行操作。另外所有需要一塊進行操作的元素也都使用 <g /> 分組標籤進行了包裹。
外圈的波紋效果外圈的波紋效果本質上就是圓的半徑慢慢放大,效果裡還伴隨了圓的邊框變窄的一個過程。其中三個圓最內層的那個是不需要動的,只需要動後面兩個即可。從設計稿中拿到結束幀的狀態之後這個動畫做起來就比較容易了。
#track-circle-2 {
animation-name: wave1;
animation-timing-function: ease-in-out;
animation-duration: 6s;
animation-iteration-count: infinite;
}
#track-circle-3 {
animation-name: wave2;
animation-timing-function: ease-in-out;
animation-duration: 6s;
animation-iteration-count: infinite;
}
@keyframes wave1 {
50% {
stroke-width: 4;
r: 219px;
}
}
@keyframes wave2 {
50% {
stroke-width: 3;
r: 274.5px;
}
}
預覽地址: https://code.h5jun.com/xovew/edit?output
主體的伸展運動這塊是整個裡面比較複雜的一部分了,不過通過拆分,我們發現實現起來也比較簡單,先實現內部元素的平移,然後再補充上整體的自轉效果即可。平移這塊沒有什麼多說的,唯一麻煩的就是通過起始幀和結束幀的位置計算出需要移動的距離而已。如圖最終白線標記的位置就是我們需要的平移位置啦。
#top-triangel { animation: topmove ease-in-out 2s infinite; }
#shadow { animation: shadowhide linear 2s infinite; }
#bottom-triangel { animation: bottommove ease-in-out 2s infinite; }
#right-triangel { animation-name: rightmove ease-in-out 2s infinite; }
@keyframes topmove {
from, to { transform: translate(0, 0); }
50% { transform: translate(-31px, -30px); }
}
@keyframes bottommove {
from, to { transform: translate(0, 0); }
50% { transform: translate(-31px, 30px); }
}
@keyframes rightmove {
from, to { transform: translate(0); }
50% { transform: translate(29px); }
}
@keyframes shadowhide {
30%, 70% { opacity: 0; }
}
對了,別忘記我們剛才的動畫拆分裡還有白色圓球和白色橫條的漸隱效果。漸隱效果可以使用 opacity 透明度來實現,不過這裡除了漸隱之外還有一個大小的變化,可能使用呼吸效果來表述會更合適一點。圓的大小就是修改半徑,橫條的大小我們直接修改寬度就可以了。
#white-ball { animation: balltransparent ease-in-out 2s infinite; }
#white-line { animation: linetransparent ease-in-out 2s infinite; }
@keyframes balltransparent {
50% {
opacity: 0;
transform: scale(0);
}
}
@keyframes linetransparent {
50% {
opacity: 0;
width: 0px;
}
}
根據剛才的動畫拆分,主體部分我們就還剩下一個自轉沒有實現了。在做這一部分的時候需要注意兩點。第一,旋轉默認是基於 SVG 畫布的左上角進行旋轉的,自轉的話一般都是基於中心旋轉,所以一定要記得設置 transform-origin 為中心點。第二,Sketch 導出的 SVG 會存在大量的 translate() 平移屬性操作,有可能是最開始設計師畫的時候是在某個位置,後來覺得不合適進行了移動,在 SVG 裡就會以平移變換體現出來。這個時候如果我們直接使用 transform 進行變換的話實際上是會複寫掉它們原本的平移的,這樣就導致了之前設置的旋轉圓心不正確的問題。
所以這種情況下需要使用聯合變換,將之前的平移變換補充到 CSS 的變換中來就可以了,這也是為什麼代碼中會多出兩個 translate() 的原因。
#main {
animation: mainrotate linear 6s infinite;
transform-origin: center center;
}
@keyframes mainrotate {
from {
transform: translate(0, 0) rotateZ(0deg) translate(-72px, -42px);
}
to {
transform: translate(0, 0) rotateZ(360deg) translate(-72px, -42px);
}
}
下面就是最終的實現效果。怎麼樣,是不是感覺已經離勝利不遠了!
預覽地址: https://code.h5jun.com/dahag/edit?output
藍球的公轉效果動畫拆分裡的最後一步就是藍球的公轉效果了。從上文我們知道,旋轉我們是可以設置旋轉圓心的。自轉是圍繞自己轉的,所以旋轉圓心是自己的中心,公轉則是圍繞太陽轉的,所以旋轉圓心是太陽的圓心,對應到我們的動效裡其實就是整個畫布的中心。
在這裡我還使用了 CSS 表達角度的另外一個單位 turn,它表示的是圈數,轉 360° 就表示 1turn。除了 turn 之外,CSS 角度還有 grad 梯度和 rad 弧度這兩個單位。grad 則是將一個圓劃分成了400等分,轉 360° 就表示 400grad。而 rad 弧度則和我們數學上的弧度表示基本一致,一個圓總共是 2πrad。
#blue-ball {
animation: spin linear 6s infinite;
transform-origin: center center;
}
@keyframes spin {
from { transform: rotate(0turn); }
to { transform: rotate(1turn); }
}
預覽地址: https://code.h5jun.com/kiqi/edit?output
後記最後將上面的代碼拼湊起來就可以實現文章開頭的動畫效果了,是不是還挺簡單的。另外在 SVG 標籤中也支持內嵌 <style> 和 <script> 標籤,所以我們可以直接將樣式內嵌在 SVG 文件中,這樣我們就可以和引用 GIF 一樣直接使用 <img> 或者背景圖片的形式使用 SVG 而不需要其他負擔,在一些不支持內嵌樣式的 Markdown 網站比如 Github 中效果奇佳哦!
文內連結https://75.team/post/svg-animation-in-action.html
關於奇舞周刊《奇舞周刊》是360公司專業前端團隊「奇舞團」運營的前端技術社區。關注公眾號後,直接發送連結到後臺即可給我們投稿。