如何藉助SVG+CSS用2個小時擼完一個網易雲音樂的動效海報(可控制速度)

泱泱發表於2019-03-06

因為平時也關注網易UEDC的訂閱號,前幾天就看到了這麼一個動效,主題是《網易雲音樂2018年度聽歌報告》,內容是一個人在努力蹬車因為構圖簡單,創意又不錯,所以就試了下用SVG+CSS動畫實現起來的難度,大概費時兩個小時左右,效率還是蠻高的,總比用AE實現起來快的多得多,下面就捋一捋實現的過程。

1. 第一步 先構圖,費時1小時

畫圖這種事情,怎麼能難得到一個美工呢。在Ai中完成,不考慮太多細節(原效果中有漸變和噪點,這裡就省略了,用純色填充來代替,畢竟人家是一個team,而我是一個人在摸索著戰鬥)。

如何藉助SVG+CSS用2個小時擼完一個網易雲音樂的動效海報(可控制速度)
圖做完之後,就要進行拆解了,需要把四個部分拆出來,這四個部分就是要做動效的部分,左大腿,左小腿,右大腿,右小腿。

如何藉助SVG+CSS用2個小時擼完一個網易雲音樂的動效海報(可控制速度)

如何藉助SVG+CSS用2個小時擼完一個網易雲音樂的動效海報(可控制速度)
這四個部分為了方便下面做動效,每一個都是要放到單獨的圖層中的,至於其他圖層順序,就不是那麼重要了。

2.第二步 給腿部增加旋轉動效,費時0.5小時

做這種涉及到關節類的實現效果,其實是個通路,因為簡而言之都是旋轉動畫,不過是增加了連動,也就是說大腿圍繞關節處(即原點)的旋轉時要帶動小腿圍繞大腿末端(小腿動畫對應的原點)的旋轉。因為有做舞動的機器人的基礎,這裡就簡單了很多,不過仍然做了一點點小的優化,沒有像做機器人時拆分成很多SVG,而是直接進行了巢狀。

首先,最最重要的一點,要獲取下面圖中這三個座標值。這是做旋轉動畫時不可或缺的tranform-origin值。

大腿的旋轉是非常簡單的旋轉動畫設定了,以左腿為例,通過對比左右腿,差不多得到的資料就是大概順時針旋轉了60度,只要在CSS中定義動畫引數如下:

/*大腿旋轉運動的動畫規則*/
@keyframes legLMove{
0% {transform: rotate(0deg)}
100% {transform: rotate(-60deg)}
}
#legL{
animation: legLMove 1s  infinite  alternate;
transform-origin:455px 235px; /*大腿旋轉運動的原點*/
}
複製程式碼

如何藉助SVG+CSS用2個小時擼完一個網易雲音樂的動效海報(可控制速度)
這樣就輕鬆的得到了大腿運動的動效。
相比之下,小腿就要複雜的多,步步拆解嘛,動畫設定的思路也是解題思路,所以,先解決的第一個問題,就是小腿跟隨大腿運動的問題。因為在匯出SVG之前,在Ai中已經把需要增加動效的部分放置再來不同的圖層裡,因此,簡化的腿部對應的DOM結構是下面這種

<!--左大腿-->
<g id="legL">
	<path d="M……z"/>
</g>
<!--左小腿-->
<g id="calfL">
    <path d="M……z"/>
</g>
複製程式碼

既然要跟隨大腿運動,那好辦,直接把左小腿整體部分放到大腿所在的組合<g>標籤中,就能保證相同的運動了。修改後的DOM結構就變成了:

<!--左大腿-->
<g id="legL">
	<path d="M……z"/>
	<!--左小腿-->
	<g id="calfL">
    	<path d="M……z"/>
	</g>
</g>
複製程式碼

如何藉助SVG+CSS用2個小時擼完一個網易雲音樂的動效海報(可控制速度)
現在,這一步已經解決了,那麼來解決下一個問題吧,如何讓小腿自由的擺動。這裡,為了確定小腿的旋轉引數,我畫了一張示意圖。

如何藉助SVG+CSS用2個小時擼完一個網易雲音樂的動效海報(可控制速度)
這張圖中,綠色的弧線就是大腿的運動軌跡,紅色的弧線是小腿的運動軌跡,最終小腿部分和大腿部分幾乎是平直狀態,因為小腿目前是跟隨大腿運動的,換句話說,它們兩個處於同一個參考系中,因此,在確定小腿的擺動幅度時,不用考慮大腿的狀態。(半透明的小腿部分就是最終小腿的狀態)。目測,逆時針旋轉,100度左右(不用那麼精確)。因此,小腿部分的動畫規則就出來了:

@keyframes calfLMove{
0% {transform: rotate(0deg)}
100% {transform: rotate(100deg)}
}
#calfL{
animation: calfLMove 1s  infinite alternate;
/*小腿對應的旋轉原點 也就是大腿的末端*/
transform-origin:343px 220px; 
}
複製程式碼

這樣,小腿在跟隨大腿進行旋轉運動的同時實現了自己的旋轉動效。(這裡實現的這種方法要比之前在做舞動的機械人時優化了很多)。

如何藉助SVG+CSS用2個小時擼完一個網易雲音樂的動效海報(可控制速度)

左腿完成了,右腿就水到渠成了,不過是一個逆向而已。記得修改DOM結構。

如何藉助SVG+CSS用2個小時擼完一個網易雲音樂的動效海報(可控制速度)
當然,這個蹬自行車的效果實現的並不好,看上去更像是踩踏板,有時間的話準備開一篇新的文章通過定義offset-rotate:var(--degMove);這種在CSS中設定隨路徑曲率的旋轉方向為變數的方法,然後javascript設定定時器改變這個值來實現(這個方法已經測試過了,js同樣也可以通過document.documentElement.style.setProperty的方法來改變SVG的CSS樣式)。此為後話。

3. 第三步 增加音符的動效,費時0.5小時

為了儘可能的和原效果保持一致,這裡把音符的動效一併加上了。為了方便檢視,我先把無關元素暫時隱藏掉,只保留音符,大概的效果是下面這種:

如何藉助SVG+CSS用2個小時擼完一個網易雲音樂的動效海報(可控制速度)
音符逐漸變大並沿路徑移動後淡化消失的過程。從這句話中,我們提煉出三個元素:沿路徑移動 | 變大 | 變淡,也就是說對應三種動效設定。 第一步,為了實現路徑動畫,我先繪製了路徑,並確保每個音符落在路徑上。

如何藉助SVG+CSS用2個小時擼完一個網易雲音樂的動效海報(可控制速度)
路徑動畫的CSS引數寫一寫:

@keyframes notePath{
0% {offset-distance:0%;}
100% {offset-distance:100%;}
}
#notePath{
offset-path:path('  '); /*繪製的路徑path對應的d屬性*/
animation:notePath 2s ease infinite;
}
複製程式碼

音符先不做縮放的處理,以中間狀態的為基準,呼叫這個動畫屬性後,就可以得到沿路徑運動的音符了**(再次強調,在路徑動畫中,一定要把元素放到畫布零位置後再匯出。)**:

如何藉助SVG+CSS用2個小時擼完一個網易雲音樂的動效海報(可控制速度)
繼續,下面疊加尺寸變化,animation屬性不需要做任何修改,但動畫規則中要增加關於縮放,也就是transform:scale()的定義

@keyframes notePath{
0% {
offset-distance:0%;
transform:scale(0.2); /*增加關於縮放的定義,起點縮小至原尺寸0.2*/
}
100% {
offset-distance:100%;
transform:scale(1.5); /*增加關於縮放的定義,終點放大至原尺寸1.5倍*/
}
}
複製程式碼

此時,音符在沿路徑移動的基礎上已經疊加了縮放的效果:

如何藉助SVG+CSS用2個小時擼完一個網易雲音樂的動效海報(可控制速度)
只差最後一步,就是透明度的變化了。通過分析動效,音符並不是在整個動畫的時間過程中遵循透明度的變小的規律,而是在變化的中途開始逐漸變淡。這裡的處理有兩個思路,正好都說一下。第一種,直接定義在統一的動畫規則中。既然是中途的變化,那我們CSS可以改成下面這種:

@keyframes notePath{
0% {
offset-distance:0%;
transform:scale(0.2);
}
50%{opacity: 1} /*在中途增加一個關於透明度的定義*/
100% {
offset-distance:100%;
transform:scale(1.5);
opacity: 0.1
}
}
複製程式碼

如何藉助SVG+CSS用2個小時擼完一個網易雲音樂的動效海報(可控制速度)
來個好玩的吧,試試騙過瀏覽器。 其實瀏覽器是很傻的,它只會乖乖的看著你的動畫規則進行解析,比如透明度這種問題,我們知道opacity:1就是完全不透明的了,但瀏覽器對於opacity定義大於1時,也會按照完全不透明來解析。想到了什麼沒?
也就是說,我們完全不用增加一個50%的關鍵幀,而是寫成下面這種:

@keyframes notePath{
0% {
offset-distance:0%;
transform:scale(0.2);
opacity: 2; /*透明度設定>1*/
}
100% {
offset-distance:100%;
transform:scale(1.5);
opacity: 0.1;
}
}
複製程式碼

對瀏覽器來說,它要執行的過程中,透明度opacity是這樣來的,整個時間段,2→0.1,所以差不多前半段是2→1(對應透明度不發生變化),後半段1→0.1(對於透明度變小),也就滿足了我們的設定。 第二種方法,雖然略顯麻煩,卻是很重要的一種方法。在上面的設定中,有一個問題,就是因為animation屬性是一個統一定義,這裡麵包含的引數非常多,比如說速率,也就是我定義為ease值的引數。如果我想讓透明度線性速率變化,怎麼辦呢?來試試給動畫屬性疊加多個規則

/*增加一個設定透明度變化的動畫規則*/
@keyframes noteOpacity{
	50%{opacity:1}
	100%{opacity:0.1}
}
複製程式碼

這時,我們要把這個透明度變化的動畫規則追加到animation的定義中,追加的方式很簡單,逗號後面增加上新的動畫規則的屬性就可以了:

animation:notePath 2s ease infinite ,noteOpacity 2s linear infinite;
/*新的動畫規則noteOpacity可以自由定義其他動畫屬性*/
複製程式碼

這種疊加多個動畫規則(當然了,你得有個前提,這些動畫規則分別定義了不同的屬性變化)在一些場景下非常有用的。以這個來做一個非常簡單的嘗試。當我把noteOpacity這個動畫規則的動畫時間由2s改成0.5s,為了看出差別,路徑動畫的時間延遲到了5s,猜猜動效會發生什麼變化?

如何藉助SVG+CSS用2個小時擼完一個網易雲音樂的動效海報(可控制速度)
正如圖中所示,這是兩個不同的動畫規則的疊加效果,因此整個路徑運動的過程中會有數次透明度的變化。 好了,讓律動的音符和我們騎自行車的動效進行合成吧!

如何藉助SVG+CSS用2個小時擼完一個網易雲音樂的動效海報(可控制速度)
粗製濫造的仿作到此為止。來個小結吧,這篇文章中兩個重要的部分:

  1. 對於連動效果,先把整體部分放入需要連動的元素的同級中,以便保持相同的動畫設定,再另行設定單獨的動畫屬性。
  2. 給動畫屬性animation增加多個不同的動畫規則的方法非常非常有用。

我也是在做CSS動畫的過程中,不斷來優化原來總結的知識體系,以後遇到好玩有趣的案例都會做一做。至於CSS變數這個,雖然是2年前的功能,對我來說,卻是新的知識點,也準備試一下和js結合之後能怎麼玩起來,畢竟要比通過innerHTML方法向CSS檔案中新增<style>標籤和內容要簡單明朗的多。

4. 加強版 來做一個可以任意加速減速的效果

本來這個支援互動增減速度的效果沒想在這個案例中實現的,但有人留言提到了嘛,那就試一下。先明確一下,加速和減速改變的是animation屬性中的動畫週期中需要的時間。在這個案例中,四個動畫時間是統一的,那既然如此,這裡不用具體的時間定義,而是寫成var(--speed)這種定義變數的方式。舉個例子,原來左腿小腿的動畫屬性如下:

animation: legLMove 1s  infinite  alternate;
複製程式碼

用全域性變數定義之後改成了:

animation: legLMove var(--speed)  infinite  alternate; 
/*用變數代替具體的時間定義*/
複製程式碼

定了變數之後,在頁面載入時,首先要給**--speed一個初始值,所以直接定義了一個變數time並將其初始值設為1,然後通過document.documentElement.style.setProperty("--speed",time+"s")這種方法給--speed**變數定義了一個初始值1s。

這裡還有方法二,就是寫成var(--speed, 1s)這種增加預設值但定義方式,但因為需要重複寫四遍,實在嫌棄麻煩,索性放棄了。

下面是最重要的部分,設定鍵盤按下的監聽事件。不考慮瀏覽器相容的話,鍵盤的監聽事件document.onkeydown在svg中同樣適用。因為只是簡單的加速和減速,所以我只指定了兩個按鍵,左右箭頭,想要得到的效果就是按下左箭頭減速,按下右箭頭加速。

對於動畫效果的加速減速,對應到動畫時間上正好相反,時間越長,速度越慢,因此,每次按下左箭頭後讓time+1就可以了,速度會相應變慢;而加速時,因為time只能是正數,所以設定成了time/(time+1)。(這裡定義的方法有很多,只是隨便選了一種)。最終的javascript部分如下:

var time=1;
//在鍵盤事件觸發之前先給一個初始設定。
document.documentElement.style.setProperty("--speed",time+"s");
document.onkeydown= function(e){
    //左鍵頭
    if(e.keyCode==37){time+=1;}
    //右箭頭
    if(e.keyCode==39){time=time/(time+1);}
    document.documentElement.style.setProperty("--speed",time+"s");
    }
複製程式碼

好了,現在可以試一下了,為了方便看出來按下鍵盤的效果,我給原來SVG的結構中增加了一個文字標籤<text>,並讓其顯示按鍵的key值。

如何藉助SVG+CSS用2個小時擼完一個網易雲音樂的動效海報(可控制速度)
從動效中可以看出來,重點觀察右下角按鍵值的key值,通過左右箭頭可以明顯的控制腳踏的速度。當然了,因為轉gif導致的卡頓,效果並不是那麼流暢。在控制檯輸出一下time值,會更加一目瞭然。因為受制於所傳動圖的尺寸,我分別按了右鍵3次和左鍵3次。

如何藉助SVG+CSS用2個小時擼完一個網易雲音樂的動效海報(可控制速度)
好了,案例終於圓滿的結束了,因為這是我第一次使用CSS變數,有用的不合時宜的地方,還請各位大神予以指正。javascript慢慢入門之後,真的發現CSS動畫愈發好玩兒了,畢竟有互動才有操縱感。在留言中,有人問過這種和Gif但區別,我想,區別大概就是在這裡吧。

CSS定義變數屬性值的方法實在是比通過建立<style>標籤的方式,通過innerHTML的方法追加樣式屬性的方法好用好用太多了。

相關文章