題目有點繞,源起最近一個專案中所需的一枚loading圖示。SVG+CSS3動畫做了那麼多,真正應用在專案中的機會少之又少,所以,抓住一切機會,即使loading也不能放過,用系統自帶菊花有辱我這一年的修煉。在最後完美做成的過程中,解決了兩個問題,第一,是非等粗交叉路徑的描邊動畫實現,第二,是多個拼接動畫的無限迴圈問題,後者困擾了許久,所以,當這個問題解決時,急於分享出來,便於其他的設計師小夥伴遇坑時一笑而過,也就是這篇文章的起因了。
1.如果只是簡單的描邊動畫
是的,我是說如果只是如果。先看下要實現的動效。
左邊是真身,很簡單的一個企業的logo,因為是這種連筆式的,所以本能的反應適合描邊動畫,動態展示logo繪製過程。右邊就是初步想法,繪製過程。看上去很簡單,啊哈,來,透過現象看本質。如果這個圖示是下面這種,對,就是我曾經的心頭好,網易雲音樂,因為隨手用鋼筆畫的,沒有用布林運算,所以略顯粗糙。這種來個描邊動畫,那簡直是分分鐘搞定的事情。 這種描邊動效通過定義stroke-dashoffset
屬性來實現(from 0; to length),非常簡單,以前的文章中也寫過戳這裡戳這裡,此處略過。好了,為什麼說這種好實現呢,因為描邊只要定義三個樣式的屬性值stroke-linecap
、stroke-linejoin
和stroke-width
就好。
好了,不說別人的logo描邊動效多好實現了,來分析一下現在的案例,難度在哪裡?非線性啊親們。如果這個圖示是下面這種的:
簡單不簡單,你就說簡單不簡單!當然,讓甲方爸爸改圖示是不現實,如果妥協做個神似形不似版的那還不如菊花了(轉行做美工久了,沒點強迫症還真是不行)。現在開始,找解決方法,突破,分析問題的能力還是有的。我想到的方法是萬能的蒙版。蒙版是個好東西啊,能遮住所有你不想看到的,那直接給路徑描邊動畫加個蒙版就好了唄。
白色蒙版是logo部分,其餘黑色的部分遮住。之所以描邊給了很粗,就是因為這個logo本身起點處較粗,需要加粗的描邊路徑經過所有的logo部分。那麼,還以為這樣就完了?2.如果只是路徑不重合的描邊動畫
是的,我是說如果只是如果。繼續舉個例子,如果是下面這種logo,這事就又簡單多了。這是個什麼,鬼知道,我就隨手搞了一個不等粗的描邊而已。
看,上面分析的使用蒙版的思路也是對的吧?輕鬆實現了不等粗的logo描邊效果。那來看看真實案例,準備好,打臉( ̄ε(# ̄)☆╰╮( ̄▽ ̄///)。效果是這樣的!!!
其實 也蠻好理解的,主要是交叉部分出了問題。當第一遍描邊動畫路過交叉點的時候,已經透過蒙版顯示了與描邊等寬的部分。
3.來吧,解決問題吧
當然,這點區區的小困難是不會讓我放棄的。既然在交叉點那裡糾纏不清,那就快刀斬亂麻,把路徑剁開就好(暴露了暴力的本性)。
每段各司其職,定義好時間延遲,ok了。簡單貼上點程式碼湊湊字數,CSS部分
/* 定義一個統一的改變stroke-dashoffset值的動畫屬性*/
@keyframes dash{
to {stroke-dashoffset: 0;}
}
@keyframes dash{
#MH_Path1{
stroke-dasharray:705; /* 705為第一段分段路徑的長度*/
stroke-dashoffset:705;
animation: dash 0.7s linear forwards; /* 0s開始,持續0.7s*/
}
#MH_Path2{
stroke-dasharray:645; /* 645為第二段分段路徑的長度*/
stroke-dashoffset:645;
animation: dash 0.6s linear 0.7s forwards; /* 延遲0.7s開始,持續0.6s*/
}
#MH_Path3{
stroke-dasharray:108; /* 108為第三段分段路徑的長度*/
stroke-dashoffset:108;
animation: dash 0.1s linear 1.3s forwards; /* 延遲1.3s開始,持續0.1s*/
}
複製程式碼
DOM部分,因為三部分蒙版圖形要被複用一次作為淺灰色logo底圖(或者也可以單獨匯出路徑,但畢竟不是最優化的方法),所以我用<symbol>
來定義三部分的圖形。
<symbol id="logo1">
<path d="" /> <!-d值對應第一部分蒙版的路徑-->
</symbol>
<symbol id="logo2">
<path d="" /> <!-d值對應第二部分蒙版的路徑-->
</symbol>
<symbol id="logo3">
<path d="" /> <!-d值對應第三部分蒙版的路徑-->
</symbol>
<!--定義三部分蒙版,用use標籤去引用 -->
<mask id="MH1"><use xlink:href="#logo1" /></mask>
<mask id="MH2"><use xlink:href="#logo2" /></mask>
<mask id="MH3"><use xlink:href="#logo3" /></mask>
<!--底層淺灰色logo-->
<g fill="#ede8e6">
<use xlink:href="#logo1" />
<use xlink:href="#logo2" />
<use xlink:href="#logo3" />
</g>
<path mask="url(#MH1)" id="MH_Path1" d="" /> <!-d值對應第一段分段路徑-->
<path mask="url(#MH2)" id="MH_Path2" d="" /><!-d值對應第二段分段路徑-->
<path mask="url(#MH3)" id="MH_Path3" d="" /><!-d值對應第三段分段路徑-->
複製程式碼
經過路徑和蒙版的剪下,得到了下面這枚半成品的loading logo。
4.或許,這裡才是真正的乾貨
看起來似乎沒有問題了,蒙版拼接+路徑拼接,交叉點的問題已然解決。但這不過是SVG+CSS3動效的活學活用,這樣的案例隨隨便便拿一個來都可以分析,不足以成文。
loading圖示算是完成了,but just done ,not perfect。我們都知道,載入的時間是不可控的,那麼,完成一次描邊動畫後,理論上應該開始下一輪,animation有個屬性是animation-iteration-count
,也就是動畫播放次數,像我們轉圈圈的菊花圖示,一般都會定義值為infinite,也就是無限迴圈,那在這個案例中,問題出在什麼地方呢?
再回過頭看我們的描邊動畫屬性的定義,我以最有代表性的第二段為例:animation: dash 0.6s linear 0.7s forwards
,後面的0.7s是動畫延遲開始的時間,在進行無縫拼接的時候,第二段描邊動畫開始的延遲時間就是第一段動畫的時間,同理,第三段動畫開始的延遲時間為動畫一加動畫二,當沒有定義執行次數時,預設執行一次。那麼,當我們加上這個無限迴圈的屬性值之後,來看看動畫變成了什麼樣子。
看上去亂七八糟,那是因為被切割的每段都在單獨執行自己的迴圈,是的,動畫執行的次數可以無限迴圈,但延遲只能被執行一次,並沒有什麼特殊的屬性可以控制在每個迴圈開始之前都執行延遲。至少現在沒有,但CSS3是不是可以考慮加上新的規範(又在浮想翩翩中,醒醒吧)。通過最常用的infinite屬性來控制無限迴圈的泡沫已然破碎,但這個問題會是無解的麼?(又在廢話,無解的話這篇文章意義何在?)
現在來想一下,控制延遲時間,除了直接在animation屬性中直接寫時間值來定義,還有什麼方法。對,就是關鍵幀@keyframes
(嗯,儼然又開啟了愉快的自問自答模式)。從現在開始,為了infinite屬性可以發揮作用,我的三部分動畫不再設任何延遲,共用相同的全程動畫時間,取而代之通過@keyframes
來控制開始和結束的時間。看一下下面這張圖或許有助於理解:
下面,我要通過控制@keyframes
的時間節點來控制每段動畫的時間區間,至於為什麼選擇45%和90%作為節點,無他,只是估摸了一下每段動畫的時間的比例,為了好計算而已。我把整個動畫時間週期設計成了2s,DOM部分沒有變化,但CSS部分需要完全重新定義。首先,通用的改變stroke-dashoffset值的動畫屬性的設定已經無用了,因為不是從0%到100%執行,而是打斷了,具體的打斷方法各個部分又有所不同。
@keyframes MH_Path1{
0%{stroke-dashoffset:705;}
45%, 100%{stroke-dashoffset: 0;}
}
/* 0s開始,持續0.9s,1.1s延遲*/
#MH_Path1{
stroke-dasharray:705;
animation: MH_Path1 2s linear both infinite;
}
@keyframes MH_Path2{
0%, 45% {stroke-dashoffset: 645;}
90%, 100%{stroke-dashoffset: 0;}
}
/* 0.9s開始,持續0.9s,0.2s延遲*/
#MH_Path2{
stroke-dasharray:645;
animation: MH_Path2 2s linear both infinite;
}
@keyframes MH_Path3{
0%, 90% {stroke-dashoffset: 108;}
100%{stroke-dashoffset: 0;}
}
/* 1.8s開始,持續0.2s*/
#MH_Path3{
stroke-dasharray:108;
animation: MH_Path3 2s linear both infinite;
}
複製程式碼
在第一段路徑中,我在45%處即執行完成了整個描邊動畫過程,而剩下的從45%到100%部分,因為沒有任何變化,所以自然而然的生成了延遲時間,對應定義45%, 100%{stroke-dashoffset:0;}
;第二段則需要同時控制開始和結束,同理,第三段則只需要控制開始時間。
是時候檢驗一下效果了:
如果是整個動畫需要延遲開始,那就簡單的多,只需要在每個animation屬性中寫入需要延遲的時間就可以了,三個不分彼此,相同的定義,完美共享。
在做這次案例的過程中,最大的收穫就是通過定義關鍵幀實現了多個拼接的動畫(或者稱之為有延遲效果的動畫)的無限迴圈問題,所以,你壓軸!