H5特效動畫中的CSS技巧


當下CSS3應用已經相當廣泛,其中重要成員之一就是CSS3動畫。並且,隨着CSS動畫的逐漸深入與普及,更復雜與細膩的動畫場景也如雨后春筍般破土而出。例如上個月做的「企業QQ-新年祝福」活動:

感謝shirley幫忙錄制上面的視頻,雖然視頻內容是手機上的顯示效果,但是,這個“企業新年祝福活動”原本只針對桌面端,移動端是后來輔助增強(增加了相當於活動頁面UV 5.7%的點擊)。而目前大多數類似頁面只針對移動端,例如其他同事實現的QQ空間5.0預約頁第二版:

QQ空間5.0預約頁第二版

 

掃碼(需登錄)或者鏈接二選一:

訪問demo戳這里

因此,需要多一點適配的技巧。但是,對於動畫效果實現,其實都是一脈相承的,最終的實現需要很多點滴積累,我這里講三個部分同學可能不知道的相關CSS技巧。

注:示例代碼的私有前綴均省略,大家自行腦補

技巧一、使用animation-play-state控制每屏動畫播放

1. 類名active與動畫觸發
首先,使用active觸發每一屏的動畫,幾乎已經約定俗成,應該也建議成為默認的行業規范。

一般做法是,當對應一屏內容進入的時候,使用JS給容器添加類名active:

  1. container.classList.add("active");

如果你做的動畫逼格較高,希望每次瀏覽這一屏內容的時候,動畫都走一遍,可以使用reflow重新觸發一下animation:

  1. container.classList.remove("active");
  2. container.offsetWidth = container.offsetWidth;
  3. container.classList.add("active");

2. 類名active與動畫控制技巧
如何具體控制動畫的播放呢?我們通常第一反應是使用下面的方法實現,動畫的完整CSS代碼在active狀態下呈現,如:

  1. .element1 { /* 尺寸與定位 */ }
  2. .element2 { /* 尺寸與定位 */ }
  3. .element3 { /* 尺寸與定位 */ }
  4. ...
  5.  
  6. .active .element1 { animate: name1 1s; }
  7. .active .element2 { animate: name2 1s; }
  8. .active .element3 { animate: name2 1s; }
  9. ...

從實現和功能上將,上面方法是很不錯的,通俗易懂,不易犯錯。不過我個人更喜歡使用配合CSS3的animation-play-state屬性對每屏動畫進行控制,實現如下:

  1. 動畫相關CSS代碼直接寫在元素上:
    1. .element1 { /* 尺寸與定位 */ animate: name1 1s; }
    2. .element2 { /* 尺寸與定位 */ animate: name2 1s; }
    3. .element3 { /* 尺寸與定位 */ animate: name3 1s; }
    4. ...
  2. 創建一個類名,如.animate,凡是使用到了animation動畫的元素都添加這個類名;
  3. 如下CSS代碼:
    1. .animate {
    2. animation-play-state: paused;
    3. }
    4. .active .animate {
    5. animation-play-state: running;
    6. }

之所以個人更喜歡后面的方法,是因為有一種“無侵入”的感覺,代碼層次清晰,控制關系明確。有利於后期的維護與擴展。

然而,使用animation-play-state還是有些需要注意的,對於IE10/IE11瀏覽器,animation-play-state是不能簡寫的。如:

  1. .element { animate: shake 4s 2s both infinite paused; }

只會讓整個CSS聲明掛掉的!如下寫法支持:

  1. .element {
  2. animate: shake 4s 2s both infinite;
  3. animation-play-state: paused;
  4. }

有人可能要奇怪了,怎么突然IE瀏覽器亂入了? 

首先,我們不能無視主流手機之Windows Phone. 其次,帥氣的翻屏動畫並不是移動端專有,桌面端也適用。稍稍用力,桌面移動全適配,何樂而不為!

技巧二、不同狀態下的連續動畫

有時候,動畫可能不是一波流,分狀態。

例如,我們的小火箭,先是淡出動畫,然后無限上下懸浮。怎么實現呢?

懸浮的火箭

關鍵點就是動畫分解與延時。

據我所知,沒辦法只使用一個keyframes關鍵幀聲明就實現這個效果,因為,這里有動畫狀態的變化:一個只執行一次的動畫和一個無限循環動畫。

怎么辦?我們可以將動畫分解,寫2個animation keyframes動畫關鍵幀描述。

  1. @keyframes fadeIn { /* ... */ }
  2. @keyframes float { /* ... */ }

然后,再分別應用這些關鍵幀動畫。如何應用呢?有2個小技巧:

1. 逗號與多animation動畫值
如下:

  1. <div class="element">小火箭</div>
  2.  
  3. .element { animation: fadeIn 1s, float .5s 1s infinite; } /* 我淡出, 需要1秒;我1秒后開始無限漂浮 */

其中float .5s 1s infinite這里的1s就是無限漂浮動畫執行延遲的時間,於是,兩個動畫完美配合,感覺就像是一個動畫。實際上,就是一個動畫,所有CSS3 animation動畫走同一個UI線程,這也是為何推薦使用CSS實現動畫效果的原因。

此寫法沒有兼容性問題,大家可以開開心心地使用。

2. 標簽嵌套與獨立動畫
我們還可以通過嵌套標簽的形式實現連續動畫,例如:

  1. <div class="element-wrap"><div class="element">小火箭</div></div>
  2.  
  3. .element-wrap { animation: fadeIn 1s; } /* 我淡出, 需要1秒 */
  4. .element { animation: float .5s 1s infinite; } /* 我1秒后開始無限漂浮 */

有人可能會奇怪了。animation本身就支持多動畫並行,你還搞個標簽嵌套,沒有任何使用的理由啊!沒錯,單純看我們這個例子,確實是這樣。但是:

① 提取公用動畫
這類多屏動畫是有N多元素同時執行不同的動畫。比方說,火箭是淡出,然后上下漂浮;火箭的火焰是淡出,然后大小變化;黑洞是淡出,然后左右隨波。你如何實現?

如果純粹借助animation語法,應該是:

  1. .element1 { animation: fadeIn 1s, float .5s 1s infinite; } /* 我淡出, 需要1秒;我1秒后開始無限漂浮 */
  2. .element2 { animation: fadeIn 1s, size .5s 1s infinite; } /* 我淡出, 需要1秒;我1秒后開始大小變化 */
  3. .element3 { animation: fadeIn 1s, move .5s 1s infinite; } /* 我淡出, 需要1秒;我1秒后開始左右移動 */

可以看到,淡出是公用的動畫效果,我們可以借助嵌套標簽,實現公用語法的合並,方面后期維護:

  1. .element-wrap { animation: fadeIn 1s; } /* 大家都1秒淡出 */
  2. .element1 { animation: float .5s 1s infinite; } /* 我1秒后無限漂浮 */
  3. .element2 { animation: size .5s 1s infinite; } /* 我1秒后忽大忽小 */
  4. .element3 { animation: move .5s 1s infinite; } /* 我1秒后左右移動 */

②避免變換沖突
有個元素動畫是邊360度旋轉、邊放大(從0放大到100%),像這種具有典型特征的動畫我們顯然要獨立提取與公用的:

  1. @keyframes spin { /* transform: rotate... */ }
  2. @keyframes zoomIn { /* transform: scale... */ }

好了,現在問題來了,變放大邊旋轉:

  1. .element { animation: spin 1s, zoomIn 1s; } /* 旋轉:啊,完蛋啦,我被放大覆蓋啦! */

由於都是使用transform, 發生了殘忍的覆蓋。當然,有好事的人會說,你使用zoom不就好了!確實,如果只是移動端,使用zoom確實棒棒噠!但是,我們這個企業活動,PC是主戰場,因此,FireFox瀏覽器(FF不識zoom)是不能無視的。

怎么辦?重新建一個名為spinZoomIn的動畫關鍵幀描述還是?

對啊,你直接外面套一層標簽不就萬事大吉了 

  1. .element-wrap { animation: spin 1s; } /* 我轉轉轉 */
  2. .element { animation: zoomIn 1s; } /* 我大大大 */

技巧三、無侵入定位和居中定位准則

1. 這里的“無侵入定位”指不受animation影響的元素定位,包含兩部分:一是不使用keyframes關鍵幀決定初始位置;二是不要使用keyframes中出現的屬性定位。

①. 不使用keyframes決定初始位置
應該都知道,CSS3 animationfill-mode可以決定元素動畫結束前后的位置,也就是也具有定位的作用。此時,可能就會有小伙伴,故作聰明,利用animation keyframes 0% {}form {}去做定位,貌似,還省了寫代碼。看上去很贊,實際上狹隘了,這對於對animation支持不佳或不支持的瀏覽器實際上是不友好的,例如Android2.3不支持animation-fill-mode, IE6-IE9不支持CSS3 animation,於是乎,當遭遇這些瀏覽器的時候,頁面動畫元素的布局實際上是毀掉的。所以,這些動畫元素定位的時候,需要使用“無侵入定位”,也就是,就算頁面沒有animation, 我也是個“標致人兒”。

②. 不使用keyframes中出現的屬性定位
舉個例子,有個球,正好定位在模塊的中心,同時有個無限旋轉效果。使用transform: translate(-50%,-50%)居中定位再合適不過了,不用我心里難受,於是,使用了transform定位。此時,沖突發生,旋轉動畫也是需要transform變換的。

  1. @keyframes spin {
  2. 0% { transform: rotate(0); }
  3. 100% { transform: rotate(360deg); }
  4. }

要么使用業界約定俗成spin覆蓋,要么另起爐灶沒法重用:

  1. @keyframes spin-trans {
  2. 0% { transform: rotate(0) translate(-50%,-50%); }
  3. 100% { transform: rotate(360deg) translate(-50%,-50%); }
  4. }

顯然,都是不合適的。建議使用傳統left/top/margin進行定位,與transform變換動畫“無侵入”。

2. 這里的“居中定位准則”包含兩部分:一是元素定位在容器中間位置;二是元素的定位方式為居中定位。

①. 元素定位在容器中間
容器以及容器內的動畫元素可以看成是一個動畫模塊,為了這個模塊可以輕松駕馭水平布局和垂直局部,里面的動畫元素形成的整體一定要在容器的中間,不要被設計稿或周圍環境影響。

舉個簡單例子,本文一開始展示的「企業QQ-新年祝福」活動。

企業產品用戶特點比較鮮明:一是訪問主要集中在桌面端,二是有70~80%用戶使用的是webkit/blink內核瀏覽器。所以,大家可以理解為何設計稿明明針對桌面端,卻有如此多細膩的動畫設計了。

故事是這樣的,桌面版做好了,1024-1224自適應,IE7以上都兼容(無侵入定位准則)(除了沒動畫),好棒!此時負責視覺的曉玲同學希望也能適配移動端,可以增加一定的傳播,我覺得挺好的,於是,決定通過技術手段,讓活動頁面能游走於桌面和移動之間,同時,保證各種動畫效果棒棒噠!

結果,發現自己留了一個坑,拿第2屏舉例,桌面版,長這樣,右側動畫內容並不是完全居中的:

 

本着高度還原設計稿的原則,所有動畫元素都經過測量定位,按照PSD中的參考線左上角(left/top),結果整體左側冒出60像素:

 

於是,問題來了,當移動端做響應式適配時候,由於容器內的動畫元素不是居中的,所以——

 

后來,進行了修改,內部動畫元素整體居中,外部容器桌面端做左側60像素偏移,於是,適配移動端時候,就正好是居中的啦。

 

②. 定位方式為居中定位
所謂“居中定位”是相對“傳統定位”而言的。Web頁面中的模塊、文字什么的默認都是相對於左上角堆砌的,所以,很自然地,我們在重構頁面,做布局,寫交互效果的時候,也都是相對左上角定位。活用元素本身的定位特性,這是很贊的也推薦這么做!但是,如果你和多元素CSS動畫打交道,可能就需要改變下慣性思維了,很重要的一點就是“從以左上角為參考點變成以中心點為參考點”。

point-to-left-toppoint-to-center

我們在實現多元素動畫效果時候,會出現兩類角色:一是容器;二是容器里面諸多動畫元素。

其中,對於容器元素,尤其在做移動端產品時候,我們很自然會讓其居中定位:

  1. .container {
  2. position: absolute; left: 50%; top: 50%;
  3. transform: translate3d(-50%, -50%, 0);
  4. }

這樣,各種尺寸的手機,我們都能讓其居中顯示(大尺寸可能需要一定的縮放)。

但是,我們有沒有想過讓容器里面的諸多動畫元素也居中定位顯示呢?

用代碼來解釋就是從左上角定位(或右上角定位):

  1. .example {
  2. position: absolute; left: 100px; top: 100px;
  3. }

變成中心點定位+ margin偏移:

  1. .example {
  2. position: absolute; left: 50%; top: 50%;
  3. margin-left: -100px; margin-top: -100px;
  4. }

有同學可能要疑問了,why? 前面一步到位不挺好的,后面這樣分兩步走豈不是多余?

在大多數情況下,我們的應用場景比較單一,或只需要玩轉移動端,或只需要駕馭桌面端,此時,上面兩種定位的優劣是看不出來的。

但是,遇到一些復雜的應用場景,尤其涉及到容器尺寸或定位方式改變的時候,后面的定位優勢就可以看出來。

比方說一開始提到的qzone5.0的例子,如果我們把容器寬度加大(實際是不會的,示意目的),如414像素:

 

會發現,宇航員和飛船在小行星之外了,也就是動畫元素不是聚攏狀態了。

所以,大家看出居中定位的優勢來了沒有:

  1. 動畫元素之間的位置關系不受容器尺寸影響;
  2. 居中特性遭遇多場景時適應性更強;

還是拿去年年底做的「企業QQ-新年祝福」活動舉例,第8屏:

其中,中間的“王強”和“馬老板”這些數據有可能是沒有的,也就是很有可能這一屏只有文字和宇航員,但同時還要保持整體垂直居中。很顯然,宇航員和火箭所在的容器不能是絕對定位,否則脫離文檔流,不能和上面元素保持合適垂直間距同時垂直居中。我們仍然有2種實現方法:

  1. 固定容器寬度,例如350像素寬,然后,里面左上角定位等,本身margin: auto水平居中;
  2. 容器不設定width大小,直接里面動畫元素居中定位;

方法1問題在於:

  1. 第7屏是類似結構,但是其動畫容器寬度不是350像素,沒法重用;
  2. 當在iPhone5/iPhone5s下,屏幕320像素寬(小於350像素),由於左上角定位,因此,整體不是居中效果;

而方法2,屏幕尺寸再小,也是居中的,只不過兩側有所剪裁。最終,移動端適配時候,我們不必關心定位問題,只要合適縮放就可以了 

 

以上兩屏示意demo戳這里

結語

首先,大家要明白,本文所展示的三個技術技巧屬於個人經驗建議,注意,是建議。里面所提到的所有解決方法都有更加直觀、通俗的實現,對於大多數的產品而言,技術的價值體現已經足夠;同時應用場景千千萬,沒有什么一方通行的方法,例如居中定位准則,有時候,可能就是需要非居中定位。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM