因為平時也關注網易UEDC的訂閱號,前幾天就看到了這麼一個動效,主題是《網易雲音樂2018年度聽歌報告》,內容是一個人在努力蹬車因為構圖簡單,創意又不錯,所以就試了下用SVG+CSS動畫實現起來的難度,大概費時兩個小時左右,效率還是蠻高的,總比用AE實現起來快的多得多,下面就捋一捋實現的過程。
1. 第一步 先構圖,費時1小時
畫圖這種事情,怎麼能難得到一個美工呢。在Ai中完成,不考慮太多細節(原效果中有漸變和噪點,這裡就省略了,用純色填充來代替,畢竟人家是一個team,而我是一個人在摸索著戰鬥)。
![如何藉助SVG+CSS用2個小時擼完一個網易雲音樂的動效海報(可控制速度)](https://i.iter01.com/images/6ca72b841b9a97c7fd880846a600f617fd3b694d0b1f6def73b68db0d8d9288f.png)
![如何藉助SVG+CSS用2個小時擼完一個網易雲音樂的動效海報(可控制速度)](https://i.iter01.com/images/3924c107ff7df3cb81246546ec7d43b621d3ee4e9eda1d27b86c58f4787f4beb.png)
![如何藉助SVG+CSS用2個小時擼完一個網易雲音樂的動效海報(可控制速度)](https://i.iter01.com/images/4dd07abdb5c6c9d053b07ebf3f7e93a2cfcc0a5b6daa9d67016f06ab60615bad.png)
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個小時擼完一個網易雲音樂的動效海報(可控制速度)](https://i.iter01.com/images/4f04122704a3ff492e974c42b8fff8cf4301c829e24d04e0944cb1f9b642a66b.gif)
相比之下,小腿就要複雜的多,步步拆解嘛,動畫設定的思路也是解題思路,所以,先解決的第一個問題,就是小腿跟隨大腿運動的問題。因為在匯出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個小時擼完一個網易雲音樂的動效海報(可控制速度)](https://i.iter01.com/images/31792e0a4f7ee01b8b8b5dcf86333ec0f1e303811210340ca816c478372dc6de.gif)
![如何藉助SVG+CSS用2個小時擼完一個網易雲音樂的動效海報(可控制速度)](https://i.iter01.com/images/dc29b6dd8939b8807bf5da8ae21105e667d82071b5e5d57574edc7f2cc05b27e.png)
@keyframes calfLMove{
0% {transform: rotate(0deg)}
100% {transform: rotate(100deg)}
}
#calfL{
animation: calfLMove 1s infinite alternate;
/*小腿對應的旋轉原點 也就是大腿的末端*/
transform-origin:343px 220px;
}
複製程式碼
這樣,小腿在跟隨大腿進行旋轉運動的同時實現了自己的旋轉動效。(這裡實現的這種方法要比之前在做舞動的機械人時優化了很多)。
![如何藉助SVG+CSS用2個小時擼完一個網易雲音樂的動效海報(可控制速度)](https://i.iter01.com/images/adc3822ceb174980bc0d3e7e405870094bacf729f8b36698963e4301a612cfba.gif)
左腿完成了,右腿就水到渠成了,不過是一個逆向而已。記得修改DOM結構。
![如何藉助SVG+CSS用2個小時擼完一個網易雲音樂的動效海報(可控制速度)](https://i.iter01.com/images/96485689567b6bd1f078330aeb1e11a57ed58d1e75d44586f1344d04da2bd044.gif)
offset-rotate:var(--degMove);
這種在CSS中設定隨路徑曲率的旋轉方向為變數的方法,然後javascript設定定時器改變這個值來實現(這個方法已經測試過了,js同樣也可以通過document.documentElement.style.setProperty
的方法來改變SVG的CSS樣式)。此為後話。
3. 第三步 增加音符的動效,費時0.5小時
為了儘可能的和原效果保持一致,這裡把音符的動效一併加上了。為了方便檢視,我先把無關元素暫時隱藏掉,只保留音符,大概的效果是下面這種:
![如何藉助SVG+CSS用2個小時擼完一個網易雲音樂的動效海報(可控制速度)](https://i.iter01.com/images/bfdcfd1408f09b393f0f1ef51a32ab972b1803aad5a8c980b76853eb0ce89bac.png)
![如何藉助SVG+CSS用2個小時擼完一個網易雲音樂的動效海報(可控制速度)](https://i.iter01.com/images/5d479385ed36900241909adb09457d249d5378a3a6d5b04c9ad11d347d2fc58b.png)
@keyframes notePath{
0% {offset-distance:0%;}
100% {offset-distance:100%;}
}
#notePath{
offset-path:path(' '); /*繪製的路徑path對應的d屬性*/
animation:notePath 2s ease infinite;
}
複製程式碼
音符先不做縮放的處理,以中間狀態的為基準,呼叫這個動畫屬性後,就可以得到沿路徑運動的音符了**(再次強調,在路徑動畫中,一定要把元素放到畫布零位置後再匯出。)**:
![如何藉助SVG+CSS用2個小時擼完一個網易雲音樂的動效海報(可控制速度)](https://i.iter01.com/images/414abcef3db0f6afbf3decab47228381ec6a2ff52e536b760788cfd284c16679.gif)
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個小時擼完一個網易雲音樂的動效海報(可控制速度)](https://i.iter01.com/images/ace07e1cd2634566b8d85fa961badf0dcc99a7ba4c1cac469a17efa5a2a9a35a.gif)
@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個小時擼完一個網易雲音樂的動效海報(可控制速度)](https://i.iter01.com/images/756f334c8958d0266b1cbb25ba22d25cf9caaf523f15fefac8dba66760b13166.gif)
也就是說,我們完全不用增加一個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個小時擼完一個網易雲音樂的動效海報(可控制速度)](https://i.iter01.com/images/cf8a7c3f3ba81331f40db32ae953b045dd38d8a956095b576a8efded2f3888e6.gif)
![如何藉助SVG+CSS用2個小時擼完一個網易雲音樂的動效海報(可控制速度)](https://i.iter01.com/images/0f62c3e8a1dd0f9f2b06a6140b918cb7595081ecd0279c6e4d43bca28b2bafed.gif)
- 對於連動效果,先把整體部分放入需要連動的元素的同級中,以便保持相同的動畫設定,再另行設定單獨的動畫屬性。
- 給動畫屬性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個小時擼完一個網易雲音樂的動效海報(可控制速度)](https://i.iter01.com/images/1e333f7dc1fcd6d375fd6886941ff32b73e0620f44ea71ced9d45be24ba519b8.gif)
![如何藉助SVG+CSS用2個小時擼完一個網易雲音樂的動效海報(可控制速度)](https://i.iter01.com/images/863dc7ddb4c2ef25ec22476afb3ede25a1e977e236b1536b0e9fdada2b6bbf5f.gif)
CSS定義變數屬性值的方法實在是比通過建立
<style>
標籤的方式,通過innerHTML的方法追加樣式屬性的方法好用好用太多了。