CSS 中的 transform,transition 和 animation 是分開(kāi)的三部分內(nèi)容,其中 transfrom 主要是控制元素變形,并沒(méi)有一個(gè)時(shí)間控制的概念,而 transition 和 animation 才是動(dòng)畫(huà)的部分,它們可以控制在一個(gè)時(shí)間段里,元素在兩個(gè)或以上的狀態(tài)切換的效果。
基本上我們會(huì)有這樣的一個(gè)簡(jiǎn)單的概念,CSS 的動(dòng)畫(huà)效果由瀏覽器控制和渲染,理論上比 JavaScript 的動(dòng)畫(huà)效果性能好,但是控制上沒(méi)有 JavaScript 那么靈活方便。
迪士尼出版的一本書(shū)中提及了動(dòng)畫(huà)效果的十二個(gè)原則,這篇文章講解得比較詳細(xì),并且將其結(jié)合到頁(yè)面動(dòng)畫(huà)中:《網(wǎng)頁(yè)動(dòng)畫(huà)的十二原則》。
transition
transition 允許我們?cè)?CSS 屬性變化時(shí)給它添加一個(gè)過(guò)度的動(dòng)畫(huà)效果。通常情況下,CSS 屬性變化是立即生效的,新的屬性值在超級(jí)短的時(shí)間內(nèi)替換掉舊的屬性值,然后瀏覽器重新繪制樣式內(nèi)容(可能是 reflow 或者 repaint)。大部分情況下會(huì)感覺(jué)樣式變化突兀,而 transition 則可以添加順滑的一個(gè)變化效果。例如:
.content {
background: magenta;
transition: background 200ms ease-in 50ms;
}
.content:hover {
background: yellow;
transition: background 200ms ease-out 50ms;
}
transition 的兼容性,不算差,基本上移動(dòng)設(shè)備都可以使用了,并且能做到漸進(jìn)增強(qiáng),支持的便有過(guò)渡效果,不支持的便是直接切換,所以可以放心使用。
transition 屬性
CSS 的 transition 有四個(gè)屬性:
-
transition-delay延遲多久后開(kāi)始動(dòng)畫(huà)
-
transition-duration過(guò)渡動(dòng)畫(huà)的一個(gè)持續(xù)時(shí)間
-
transition-property執(zhí)行動(dòng)畫(huà)對(duì)應(yīng)的屬性,例如color,background等,可以使用all來(lái)指定所有的屬性
-
transition-timing-function隨著時(shí)間推進(jìn),動(dòng)畫(huà)變化軌跡的計(jì)算方式,常見(jiàn)的有:linear,ease,ease-in,ease-out,cubic-bezier(...) 等。詳細(xì)參考:transition-timing-function,里邊有各個(gè)效果的簡(jiǎn)單例子。
這四個(gè)屬性可以簡(jiǎn)寫(xiě)成為:
.class {
transition: <property> <duration> <timing-function> <delay>
}
例如前邊的那個(gè)例子,當(dāng) .content 元素 hover 時(shí),50 毫秒后背景顏色從 magenta 漸變到 yellow,持續(xù)時(shí)間 200 毫秒,使用的是 ease-out 的算法。留意下:transition 生效的是對(duì)應(yīng)的選擇器的屬性,例如 .content:hover 中的 transition 便是從 .content 的 magenta 到 yellow 過(guò)渡效果的控制,而 .content 中的 transition則是控制不 hover 時(shí),背景顏色從 yellow 到 magenta 的變化過(guò)程。
all 這個(gè)屬性值是這樣的,它對(duì)應(yīng)選擇器下的元素的所有 CSS 屬性生效,無(wú)論在哪里聲明的 CSS 規(guī)則,并不局限于在同個(gè)代碼塊下。
如果需要不同屬性對(duì)應(yīng)不同的效果,可以這么來(lái)寫(xiě):
.demo {
transition-property: all, border-radius, opacity;
transition-duration: 1s, 2s, 3s;
/* 當(dāng)這樣使用時(shí),確保 all 在第一個(gè),因?yàn)槿绻?all 在后邊的話,它的規(guī)則會(huì)覆蓋掉前邊的屬性 */
}
transition 的 none 屬性較少用到,一般用于移除原本有的動(dòng)畫(huà)效果。none 沒(méi)法和逗號(hào)一起使用來(lái)移除特定屬性的動(dòng)畫(huà)效果,只能直接干掉 transition,如果要移除特定的屬性效果,可以重寫(xiě) transition 而不把要移除的屬性寫(xiě)進(jìn)去,或者比較 trick 的做法是設(shè)置 duration 為 0。
并不是所有的 CSS 屬性都是可以添加 transition 效果的。詳細(xì)可以參考文檔:animatable properties??赡芙?jīng)常遇到的就是 display 這個(gè)屬性并不能添加 transition 效果,你可以考慮使用 visibility 或者后邊會(huì)提及的 animation。
關(guān)于 transition-timing-function 的各個(gè)算法的一個(gè)變化曲線是怎么樣的,我們可以使用 chrome 的開(kāi)發(fā)者工具來(lái)看一下,CSS 中你編寫(xiě)了對(duì)應(yīng)的 transition 后,把鼠標(biāo)移到 transition-timing-function 的那個(gè)值前邊,如下圖:

transition 相關(guān)的事件
transitionend 事件會(huì)在 transition 動(dòng)畫(huà)結(jié)束的時(shí)候觸發(fā)。通常我們會(huì)在動(dòng)畫(huà)結(jié)束后執(zhí)行一些方法,例如繼續(xù)下一個(gè)動(dòng)畫(huà)效果或者其他。Zepto.js 中的動(dòng)畫(huà)方法都是使用 CSS 動(dòng)畫(huà)屬性來(lái)處理,而其中動(dòng)畫(huà)運(yùn)行后的回調(diào)便應(yīng)該是使用這個(gè)事件來(lái)處理。
transitionend 事件觸發(fā)時(shí)會(huì)傳入一些動(dòng)畫(huà)相關(guān)的參數(shù),例如:propertyName,elapsedTime,詳細(xì)內(nèi)容可以參考:transitionend。
transition 應(yīng)用
transition 在很多 UI 框架中是很常見(jiàn)的屬性,當(dāng)我們開(kāi)發(fā)一個(gè)交互效果的時(shí)候,從某個(gè)狀態(tài)到達(dá)另外一個(gè)狀態(tài)時(shí),transition 可以使得這個(gè)過(guò)程變得更加舒適和順滑。例如上邊的 hover 時(shí)的背景顏色的切換,控制元素的顯示和隱藏時(shí)使用 opacity 來(lái)實(shí)現(xiàn)漸隱漸現(xiàn)。
當(dāng) transition 配合上 transform 提供的多樣化的元素變化能力后,便可以繪制出很多有趣的交互漸變效果了。最近使用過(guò)程中做的一個(gè)簡(jiǎn)單效果的例子,點(diǎn)擊查看。
很常見(jiàn)還有表單 input 報(bào)錯(cuò)時(shí)邊框變紅,按鈕 hover 時(shí)背景漸變等,很多的 CSS 交互效果會(huì)因?yàn)?transition 變得更加自然。
animation
雖然 transition 已經(jīng)提供了很棒的動(dòng)畫(huà)效果了,但是我們只能夠控制從一個(gè)狀態(tài)到達(dá)另外一個(gè)狀態(tài),沒(méi)法來(lái)控制多個(gè)狀態(tài)的不斷變化,而 animation 而幫助我們實(shí)現(xiàn)了這一點(diǎn)。使用 animation 的前提是我們需要先使用@keyframes 來(lái)定義一個(gè)動(dòng)畫(huà)效果,@keyframes 定義的規(guī)則可以用來(lái)控制動(dòng)畫(huà)過(guò)程中的各個(gè)狀態(tài)的情況,語(yǔ)法大抵是這個(gè)樣子:
@keyframes W {
from { left: 0; top: 0; }
to { left: 100%; top: 100%; }
}
@keyframes 關(guān)鍵詞后跟動(dòng)畫(huà)的名字,然后是一個(gè)塊,塊中有動(dòng)畫(huà)進(jìn)度的各個(gè)選擇器,選擇器后的塊則依舊是我們常見(jiàn)的各個(gè) CSS 樣式屬性。
在這里,控制動(dòng)畫(huà)的整個(gè)過(guò)程的選擇器很重要,語(yǔ)法相對(duì)簡(jiǎn)單,你可以使用 from 或者 0% 來(lái)表示起始狀態(tài),而 to 或 100% 來(lái)表示結(jié)束狀態(tài)。中間的部分你都可以使用百分比來(lái)進(jìn)行表示。選擇器后的塊則是在到達(dá)這個(gè)進(jìn)度狀態(tài)時(shí)元素的樣式應(yīng)該是怎么樣的,整個(gè)的過(guò)渡動(dòng)畫(huà)在這個(gè)的控制基礎(chǔ)上由瀏覽器去繪制。
同樣地,不是所有的屬性都可以有動(dòng)畫(huà)效果,MDN 維護(hù)了一份CSS動(dòng)畫(huà)的屬性列表可供參考。
通常來(lái)說(shuō),多個(gè)狀態(tài)下的相同屬性的值應(yīng)該是可以取到它們的中間值的,例如 left 從 0% 到 100%,如果沒(méi)法取到中間值,如 height 從 auto 到 100px,有可能出現(xiàn)奇怪的一些狀況,并且不同瀏覽器對(duì)此的處理也不盡相同,所以請(qǐng)盡量避免這種情況。
animation 屬性
animation的屬性比 transition 多,如下:
-
animation-name你需要的動(dòng)畫(huà)效果的@keyframes的名字。
-
animation-delay和transition-delay一樣,動(dòng)畫(huà)延遲的時(shí)間。
-
animtaion-duration和transition-duration一樣,動(dòng)畫(huà)持續(xù)的時(shí)間。
-
animation-direction動(dòng)畫(huà)的一個(gè)方向控制。
默認(rèn)是normal,如果是上述的 left 從 0% 到 100%,那么默認(rèn)是從左到右。如果這個(gè)值是reverse,那么便是從右到左。
由于 animation 提供了循環(huán)的控制,所以還有兩個(gè)值是 alternate 和 alternate-reverse,這兩個(gè)值會(huì)在每次循環(huán)開(kāi)始的時(shí)候調(diào)轉(zhuǎn)動(dòng)畫(huà)方向,只不過(guò)是起始的方向不同。
例如還是 left 的例子,假設(shè)設(shè)置了 animation-direction: alternate; animation-iteration-count: infinite;,那么這個(gè)元素從左到右移動(dòng)后,便調(diào)轉(zhuǎn)方向,從右到左,如此循環(huán)。
-
animation-fill-mode這個(gè)屬性用來(lái)控制動(dòng)畫(huà)前后,@keyframes中提供的 CSS 屬性如何應(yīng)用到元素上。默認(rèn)值是none,還有其他三個(gè)選擇:forwards,backwards,both。
假設(shè)是 none,那么動(dòng)畫(huà)前后,動(dòng)畫(huà)中聲明的 CSS 屬性都不會(huì)應(yīng)用到元素上。即動(dòng)畫(huà)效果執(zhí)行后,元素便恢復(fù)正常狀態(tài)。
如果是 forwards,那么動(dòng)畫(huà)結(jié)束后,會(huì)把最后狀態(tài)的 CSS 屬性應(yīng)用到元素上,即保持動(dòng)畫(huà)最后的樣子。而 backwards 則相反,both 則都會(huì),計(jì)算得出最后的一個(gè)結(jié)果。
-
animation-timing-function和transition-timing-function一樣,動(dòng)畫(huà)變化軌跡的算法。
-
animation-iteration-count動(dòng)畫(huà)循環(huán)次數(shù),如果是infinite則無(wú)限次。有趣的是,支持小數(shù),即 0.5 表示動(dòng)畫(huà)執(zhí)行到一半。
-
animation-play-state動(dòng)畫(huà)執(zhí)行的狀態(tài),兩個(gè)值running或者paused,可以用來(lái)控制動(dòng)畫(huà)是否執(zhí)行。
上述這些屬性可以簡(jiǎn)寫(xiě)為:
.class {
animation: <duration> <timing-function> <delay> <iteration-count> <direction> <fill-mode> <play-state> <name>
}
略長(zhǎng),當(dāng)然,平時(shí)使用中可能是省略部分參數(shù)的。
animation 需要留意的東西
優(yōu)先級(jí)
記得 CSS 中的層疊概念么,優(yōu)先級(jí)高的屬性會(huì)覆蓋優(yōu)先級(jí)低的屬性,當(dāng) animation 應(yīng)用到元素中時(shí),動(dòng)畫(huà)運(yùn)行過(guò)程中,@keyframes 聲明的 CSS 屬性?xún)?yōu)先級(jí)最高,比行內(nèi)聲明 !important 的樣式還要高?,F(xiàn)在瀏覽器的實(shí)現(xiàn)是這樣子的,但是標(biāo)準(zhǔn)文檔中的說(shuō)法應(yīng)該是可以被 !important 聲明的屬性所覆蓋。
多個(gè)動(dòng)畫(huà)的順序
由于 animation-name 是可以指定多個(gè)動(dòng)畫(huà)效果的,所以這里便會(huì)出現(xiàn)動(dòng)畫(huà)的一個(gè)順序問(wèn)題。后指定的動(dòng)畫(huà)會(huì)覆蓋掉前邊的,例如:
#colors {
animation-name: red, green, blue; /* 假設(shè)這些 keyframe 都是修改 color 這個(gè)屬性 */
animation-duration: 5s, 4s, 3s;
}
上述代碼的動(dòng)畫(huà)效果會(huì)是這樣:前 3 秒是 blue,然后接著 1 秒是 green,最后 1 秒是 red。整個(gè)覆蓋的規(guī)則是比較簡(jiǎn)單的。
display 的影響
如果一個(gè)元素的 display 設(shè)置為 none,那么在它或者它的子元素上的動(dòng)畫(huà)效果便會(huì)停止,而重新設(shè)置 display 為可見(jiàn)后,動(dòng)畫(huà)效果會(huì)重新重頭開(kāi)始執(zhí)行。
animation 相關(guān)事件
我們可以通過(guò)綁定事件來(lái)監(jiān)聽(tīng) animation 的幾個(gè)狀態(tài),這些事件分別是:
- animationstart 動(dòng)畫(huà)開(kāi)始事件,如果有 delay 屬性的話,那么等到動(dòng)畫(huà)真正開(kāi)始再觸發(fā),如果是沒(méi)有 delay,那么當(dāng)動(dòng)畫(huà)效果應(yīng)用到元素時(shí),這個(gè)事件會(huì)被觸發(fā)。
- animationend 動(dòng)畫(huà)結(jié)束的事件,和 transitionend 類(lèi)似。如果有多個(gè)動(dòng)畫(huà),那么這個(gè)事件會(huì)觸發(fā)多次,像上邊的例子,這個(gè)事件會(huì)觸發(fā)三次。如果 animation-iteration-count 設(shè)置為
infinite,那么這個(gè)事件則不會(huì)被觸發(fā)。
- animationiteration 動(dòng)畫(huà)循環(huán)一個(gè)生命周期結(jié)束的事件,和上一個(gè)事件不一樣的是,這個(gè)在每次循環(huán)結(jié)束一段動(dòng)畫(huà)時(shí)會(huì)觸發(fā),而不是整個(gè)動(dòng)畫(huà)結(jié)束時(shí)觸發(fā)。無(wú)限循環(huán)時(shí),除非 duration 為 0,否則這個(gè)事件會(huì)無(wú)限觸發(fā)。
animation event 相關(guān)的屬性可以參考:animationEvent。
animation 應(yīng)用
animation 可以實(shí)現(xiàn)控制在多個(gè)狀態(tài)下進(jìn)行動(dòng)畫(huà)切換,所以應(yīng)用的場(chǎng)景比 transition 要廣泛得多,可以使用 animation 實(shí)現(xiàn)大量的動(dòng)效,具體可以查看下 animate.css這個(gè)庫(kù)。
我上邊提到的 簡(jiǎn)單例子 中也包括了一個(gè)簡(jiǎn)單地使用 animation 來(lái)縮放字體,和一個(gè)簡(jiǎn)單進(jìn)度條的例子。
CSS 動(dòng)畫(huà)的性能
現(xiàn)在越來(lái)越多的頁(yè)面開(kāi)發(fā)是面向移動(dòng)端,所以我們會(huì)更加關(guān)注性能方面的問(wèn)題。瀏覽器繪制動(dòng)畫(huà)的過(guò)程中,涉及的主要是 Layout,Paint,Composite 的處理,當(dāng)一個(gè)動(dòng)畫(huà)觸發(fā)的瀏覽器處理越少,影響的區(qū)域越少,那么便消耗越低,性能上越好。
我們可以參考這個(gè) CSS Triggers 網(wǎng)站提供的列表,這里展示了修改屬性時(shí)對(duì)應(yīng)的瀏覽器內(nèi)核所需要做的處理。簡(jiǎn)單概括來(lái)說(shuō),動(dòng)畫(huà)盡量少涉及布局相關(guān)的調(diào)整,因?yàn)椴季稚弦坏┳兓?,?huì)涉及外部元素和內(nèi)部元素的位置調(diào)整,對(duì)瀏覽器的消耗相當(dāng)大,而在現(xiàn)代瀏覽器中,擁有比較好動(dòng)畫(huà)性能的屬性就是 transform 和 opacity,建議需要?jiǎng)赢?huà)的時(shí)候多往這兩個(gè)屬性考慮,例如字體要放大,避免使用 font-size,而是用 transform: scale()等。
我們可以使用 will-change 來(lái)聲明即將變化的屬性,這可以讓瀏覽器提前做一些優(yōu)化工作,關(guān)于這個(gè)屬性,更多內(nèi)容可以參考: will-change。值得留意的是,別濫用 will-change,在太多的元素上使用或者應(yīng)用太多的屬性都會(huì)導(dǎo)致瀏覽器資源浪費(fèi)。
Chrome 瀏覽器的開(kāi)發(fā)者工具提供的 Timeline 工具可以幫助我們來(lái)查看頁(yè)面渲染的一個(gè)性能表現(xiàn)情況,如下圖:

Timeline 可以用來(lái)獲取腳本執(zhí)行性能,網(wǎng)絡(luò)請(qǐng)求性能等的表現(xiàn)數(shù)據(jù),但是這里我們只是關(guān)于動(dòng)畫(huà)渲染,所以只是勾選了 paint。我們可以看到渲染可以保持在 60 FPS,便不會(huì)感覺(jué)到卡頓。當(dāng)渲染 FPS 過(guò)低的時(shí)候,圖示那里會(huì)出現(xiàn)紅色的提示,通過(guò)這個(gè)工具可以幫助我們?cè)谛枰臅r(shí)候做針對(duì)性的優(yōu)化。