完整解析 CSS 動畫 ( CSS Animation )
隨著 CSS3 的普及,過去許多看似酷炫的效果,逐漸也都能透過 CSS 來實作,這篇文章將會針對 CSS 動畫進行完整的使用探討,從基礎的使用,一直到 JavaScript 的操作方法都會介紹,希望能讓大家 ( 其實是自己 ) 在使用上更加得心應手。
CSS 動畫起手式
不論是使用什麼軟體,只要是製作動畫,一定少不了「關鍵影格 keyframe」,透過許多關鍵影格的組合,加上瀏覽器自動運算當中的漸變過程,就成為我們熟知的 CSS 動畫。假設有個 div 要加入 CSS 動畫效果,只需要按照下面的寫法,就能套用名為 oxxo 的動畫 ( 以下將統稱 @keyframes 為「動畫」 ),執行後就會花兩秒的時間,往右移動到 100px 的位置,動畫結束後會再跳回 0px 停止不動。
div{
position:absolute;
left:0;
width:50px;
height:50px;
background:#f00;
animation-name:oxxo;
animation-duration:2s;
}
@keyframes oxxo{
from{
left:0;
}
to{
left:100px;
}
}
從上面的例子可以看到,使用了 from 和 to,在 CSS 動畫裡,from 表示起始,也可以用 0% 表示,to 表示結束,也可以用 100% 表示,因為數值範圍是 0%~100%,所以超過這個範圍的都是錯誤的。此外,比較需要注意的有以下三點:
若沒有開頭結尾,會自動演算出來
基本上一段動畫一動會有開頭與結尾,如果我們只寫了 0%~50% 或 50%~100%,那麼開頭與結尾將會由程式自動演算出來,當然結果是不是如果們所想的就不盡然,最好還是加上開頭結尾。
不可單獨使用數字,一定要加入 %
舉例來說,我們習慣設定寬度 0px 的時候直接寫 0,但對於動畫來說一定要加上 % 為單位,例如 0%、50%、100%。
如果同樣的百分比,則相同屬性會後蓋前
動畫的百分比表示關鍵影格的位置,0% 表示第一格,50% 表示中間,100% 表示最後一格,因此如果同樣有兩個 50%,內容的屬性也相同,則後方的 50% 會蓋掉前方的 50%,如果兩個屬性不同,則會在 50% 時同時執行。
這個例子會在動畫從 0% 到 50% 時,背景從白色變黑色。( 紅色被後面蓋掉了 )
0%{ background:#fff; } 50%{ background:#f00; } 50%{ background:#000; }
這個例子會在動畫從 0% 到 50% 時,背景從白色變紅色,文字從白色變成黑色。( 文字屬性與背景屬性同時在 50% 呈現 )
0%{ background:#fff; color:$fff; } 50%{ background:#f00; } 50%{ color:#000; }
CSS 動畫屬性總覽 ( CSS Animation Properties )
了解如何使用之後,就要來看看有哪些屬性可以設定,CSS 動畫共有八種屬性和一個八種屬性集合的簡短屬性縮寫,這邊先把屬性一次列出,下面會繼續詳細介紹用法。
屬性 | 說明 |
---|---|
animation-name | 動畫名稱 |
animation-duration | 動畫持續時間,預設 0,單位 s 或 ms。 |
animation-delay | 動畫延遲播放時間,預設 0,單位 s 或 ms。 |
animation-iteration-count | 動畫播放次數,預設 1。 其他還有 infinite。 |
animation-timing-function | 動畫加速度函式,預設 ease。 其他還有: linear、ease-in、ease-out、ease-in-out step-start、step-end、steps(int,start/end)、cubic-bezier(n,n,n,n)。 |
animation-direction | 動畫播放方向,預設 normal。 其他還有 reverse、alternate、alternate-reverse。 |
animation-fill-mode | 動畫播放前後模式,預設 none。 其他還有 forwards、backwards、both。 |
animation-play-state | 動畫播放或暫停狀態,預設 running。 其他還有 paused。 |
這八種屬性,亦可透過 animation 的屬性,做簡短的縮寫,用法如下:
animation:name duration | timing-function | delay | iteration-count | direction | fill-mode | play-state;
縮寫除了在代碼上簡短許多,更可以讓「同一個元素套用多組動畫」,用法只需要在後方用逗點分隔即可,如果有興趣可以參考我在 2014 年寫的文章,熟練 CSS 動畫以後所做的效果真的很不賴呀!
(屬性) animation-name 動畫名稱
animation-name 表示動畫名稱,既然要做動畫就得先對這個動畫命名,由於 CSS 骨子裡也是程式語言,仍保有著程式碼的邏輯思維,所以命名上須遵照以下幾點原則,使用上也比較不會發生問題,幾個命名原則如下:
名稱可以加引號、亦可不加引號
如同下面這段程式碼所表示,兩種寫法都可以代表動畫名稱。
@keyframes oxxo { ... } @keyframes "oxxo" { ... }
如果同名,以後面的名稱為主:
CSS 的權重設計邏輯為「後蓋前」,如果以上面的例子,後面有加引號的 "oxxo", 將會取代掉前面沒加引號的 oxxo 的動畫。
@keyframes oxxo { ... } @keyframes "oxxo" { ... } /* 顯示時會採用這個 */
名稱大小寫有區別
大小寫在程式碼內是截然不同的,然而 CSS 動畫也是一樣,如果大寫小不同,會視為不同的動畫。( 其實 oxxo 跟 ooxx 也是截然不同的 XD )
@keyframes oxxo { ... } @keyframes OXXO { ... }
特殊字元不能使用,但是用引號框起來就可以
CSS 動畫有兩個保留字:initial 和 None,因此我們在命名上不能用這兩個字作為動畫的名稱,如果真的要用,只要引號將關鍵字框起來即可。
@keyframes initial { ... } /* 不可使用 */ @keyframes None { ... } /* 不可使用 */ @keyframes "initial" { ... } /* 可以用 */ @keyframes "None" { ... } /* 可以用 */
(屬性) animation-duration 動畫持續時間
animation-duration 指的是「播放一次」動畫需要的時間,單位為 秒 ( s ) 或毫秒 ( ms ),預設值 0s,如果沒有設定或將其設為 0s,就不會播放動畫,基本上和 animation-name 動畫名稱都是屬於一定要有的屬性。
animation-duration:5s;
(屬性) animation-iteration-count 動畫播放次數
animation-iteration-count 表示動畫播放的次數,預設值為 1 次,如果設定為 infinite 動畫就會無止盡的播放下去。
animation-iteration-count:infinite;
(屬性) animation-delay 動畫延遲播放時間
animation-delay 代表的是動畫要延後多久才開始播放,單位為 秒 ( s ) 或毫秒 ( ms ),預設值 0s,如果沒有設定或將其設為 0s,動畫就會直接播放不會延遲,但比較特別的是,如果將延遲播放時間設定為「負值」,例如 -1s、-2s,得到的結果就「不會延遲,而是快轉」,假設一段動畫要 5 秒,animation-delay 設定為 -2s,那麼動畫將會直接從第二秒的位置開始播放,播放三秒後停止 ( 類似 5-2=3 的概念 )。
以下面的例子來說,三種顏色的方塊都是 5 秒動畫,而藍色的 animation-delay 設為 2s,紅色的 animation-delay 設為 -2s,綠色則完全不設定 animation-delay,就會得到這個結果:藍色:慢兩秒移動,紅色:直接跳到已經移動兩秒的地方開始移動,綠色:一開始就移動。
div {
width:50px;
height:50px;
position:absolute;
animation-name: oxxo;
animation-duration:5s;
}
#red{
background:#f00;
top:0;
animation-delay:-2s;
}
#green{
background:#0a0;
top:60px;
}
#blue{
background:#00c;
top:120px;
animation-delay:2s;
}
@keyframes oxxo {
from {
margin-left: 0px;
}
to {
margin-left: 200px;
}
}
(屬性) animation-timing-function 動畫加速度函式
如果你對我的這篇文章 CSS3 Cubic Bezier 有印象,裡頭就有提到 CSS 動畫加速度函式裡的 Cubic Bezier,不過 animation-timing-function 不只有 Cubic Bezier,還有其他幾個設定選項:
linear
線性,沒有任何加速減速,在 Cubic Bezier 的圖形表現為斜率 1 的直線。
ease、ease-in、ease-out、ease-in-out
具有加速減速的動畫,透過 ease 的加減速表現,能讓動畫更為流暢,比較不會像 linear 一樣硬梆梆,下圖是在 Cubic Bezier 的圖形表現。( ease 為預設值 )
step-start、step-end:
step 系列算是比較少用的設定,顧名思義它就是「一步一步」,如果設定 step-start,則會按照關鍵影格的順序一格格進行,不會有中間的演算動畫,而 step-start 和 step-end 的差異在於 step-start 會把第一格關鍵影格的 output progression ( y 軸 ) 數值直接拉到 1,從 (1, 0) 開始,接著到 (1, 1) 結束,所以「看不到第一格」,然而 step-end 則是從 (0, 0) 開始一直到 (0, 1) 的最後一格才突然變成 (1, 1),所以也導致「看不到最後一格」。
如果覺得有點繞口,可以看一下 W3C 所定義的圖片說明:
舉例來說,下面這個動畫,第一格是紅色,最後一格是黑色,如果我們設定 step-start,看到形狀的會是略過第一格,從綠色開始往左移動四次,每次 50px,若設定為 step-end,則形狀會往左移動四次,每次 50px,但不會有最後一格黑色。
@keyframes oxxo{ 0%{ left:0; background:#f00; } 25%{ left:50px; background:#0f0; } 50%{ left:100px; background:#00f; } 75%{ left:150px; background:#ff0; } 100%{ left:200px; background:#000; } }
steps(int,start|end)
steps(int,start|end) 算是剛剛 step-start 和 step-end 的進化型,換句話說 step-start 等同於 steps(1,start),而 step-end 等同於 steps(1,end),因為都只走了「一步」,如果我們把 int 步數增加,就會看到每個關鍵影格之間多了一些演算出來的影格,當然如果步數設定越多,看到的動畫也會越流暢 ( 但如果要這麼做,只要不用 step 就好了 )
舉例來說,如果把上面的 step-start 改為
steps(3,start)
,step-end 改為steps(3,end)
,就會得到下圖的結果。如果熟練 steps 的用法,就能夠很簡單的使用 sprite 圖片來做動畫,什麼是 sprite 圖片 呢?就是將許多圖案集合成一張圖,接著透過 CSS 的語法使這些圖案分別呈現在網頁裡,這樣就能大幅減少多張圖片載入的 request 數量。
上圖是一張經典的 sprite 圖片 ( Leland Stanford 所拍攝 ),只要透過 CSS 動畫的 steps,我們也能很簡單的讓圖片中的馬兒跑起來。
#hourse { width:186px; height:141px; position:absolute; background-image:url("hourse.jpg"); animation-name: oxxo; animation-duration:1s; animation-iteration-count:infinite; animation-timing-function:step-start; } @keyframes oxxo{ 0%{ background-position:-15px -13px; } 6.25%{ background-position:-210px -13px; } /* 12.5% ~ 93.75% 請看範例原始碼 */ 100%{ background-position:-592px -470px; } }
cubic-bezier(n,n,n,n):
cubic-bezier 表示動畫開始與結束速度的貝茲曲線,詳細可以參考我的這篇 CSS3 Cubic Bezier 教學,或是直接到「cubic-bezier」這個網站實際操作看看,不過 CSS 動畫說穿了,單純靠 CSS 仍然無法製作很複雜的動畫效果,雖然 cubic-bezier 很強大,但真的拿來實戰的機會卻沒有很高,只要知道用法,需要用到的時候可以用就相當足夠囉!
(屬性) animation-direction 動畫播放方向
- normal:正常播放,從 0% 到 100% ( 預設值 )。
- reverse:反轉播放,從 100% 到 0%。
- alternate:正反轉輪流播放,奇數次為 0% 到 100%,偶數次為 100% 到 0%,若動畫播放次數只有一次就只會正常播放。
- alternate-reverse:alternate 的相反,奇數次為 100% 到 0%,偶數次為 0% 到 100%,若動畫播放次數只有一次就只會反轉播放。
下面這段程式碼所產生的效果:正方形從左邊跑到右邊,然後再從右邊跑到左邊,無限次數的一直動作。
div {
width:50px;
height:50px;
position:absolute;
animation-name: oxxo;
animation-duration:2s;
animation-iteration-count:infinite;
animation-direction:alternate;
}
(屬性) animation-fill-mode 動畫播放前後模式
- none:預設值,不論動畫播放次數,結束後一律返回原始狀態。
- forwards:動畫結束後,保持在最後一個影格狀態。
- backwards:動畫結束後,保持在第一個影格狀態 ( 但實際測試和 none 效果一樣 )。
- both:依據動畫的次數或播放方向,保持在第一個影格或最後一個影格狀態,相當實用。
下面這段程式碼所產生的效果:正方形會一起往左移,動畫停止後紅色正方形留在後方、藍色正方形跳回前方。
.test {
width:50px;
height:50px;
position:absolute;
animation-name: oxxo;
animation-duration:2s;
}
#red{
background:#f00;
animation-fill-mode:forwards;
}
#blue{
top:60px;
background:#06f;
}
@keyframes oxxo{
0%{
left:0;
}
100%{
left:200px;
}
}
(屬性) animation-play-state 動畫播放或暫停狀態
- running:預設值,表示動畫運行。
- paused:表示動畫暫停。
以剛剛提到的「跑馬動畫」為例,透過動畫播放或暫停的屬性,搭配滑鼠 hover 的效果,就能很輕鬆地實現「滑鼠移到區塊上才出現動畫」的效果。( 範例使用剛剛的跑馬動畫修改 )
#hourse {
width:186px;
height:141px;
background-image:url("hourse.jpg");
animation-name: oxxo;
animation-duration:1s;
background-position:-15px -13px;
animation-iteration-count:infinite;
animation-timing-function:step-start;
animation-play-state:paused;
}
#hourse:hover{
animation-play-state:running;
}
Animation Events
Animation Events CSS 動畫行為是玩 CSS 動畫比較容易忽略的部分,但其實如果熟悉他的行為指令,許多動態效果,在 JavaScript 的部份就可以寫得更為簡潔,也更容易玩一些網頁的動畫特效,Animation Events 有以下四種:
- animationstart:當動畫開始。
- animationend:當動畫結束。
- animationiteration:當動畫重複。
- animationcancel:當動畫中止 ( 目前還不支援 )。
單就字面上其實還滿容易理解的,下面的例子會讓動畫播放兩次後停止,並會在動畫下面放上一個字串,用 JavaScript 偵測並顯示現在動畫的狀態。
var hourse = document.getElementById('hourse');
var text = document.getElementById('text');
hourse.addEventListener('animationstart',function(){
text.innerHTML = '動畫開始';
});
hourse.addEventListener('animationend',function(){
text.innerHTML = '動畫結束';
});
hourse.addEventListener('animationiteration',function(){
text.innerHTML = '動畫重複';
});
hourse.addEventListener('animationcancel',function(){
text.innerHTML = '動畫暫停';
});
CSSKeyframesRule
在網頁的 document 裡有個屬性叫做 styleSheets,這個屬性會將一個網頁裡所有用到的樣式列出,舉例來說如果你正在瀏覽我的這個網頁 ( 就是正在閱讀的這頁 ),你可以把網頁開發者工具打開,在 console 輸入document.styleSheets
,就會看到網站所有使用到的 CSS 依序列出。
如果你覺得上面的例子 CSS 太複雜,可以打開下面這個範例頁面,一樣在 console 輸入document.styleSheets
,把呈現出來的結果打開,會看到在 rules 裡包含兩個規則,分別是 id 是 test 的 div,以及名為 oxxo 的動畫規則:CSSKeyframesRules。
再把 CSSKeyframesRule 展開,就能夠看到 cssRules 裡有三個 CSSKeyframesRule,為什麼要看這個呢?因為如果我們想用 JavaScript 操控 CSS 動畫,通常都是直接更換動畫名稱,不然就是修改、添加或刪除 class 來實現,然而如果知道 CSSKeyframesRule,就能夠直接修改動畫內容。
說到要修改動畫內容,就需要用到下列幾種方法:
- findRule():找出對應規則。
- appendRule():添加規則。
- deleteRule():刪除規則。
舉例來說,下面這段程式碼,會在網頁打開的時候,在 console 裡呈現動畫的規則,接著點選按鈕後會出現原本 50% 的 keyframe 規則,同時會移除這個 keyframe 並添加新的 50% keyframe,最後呈現的結果,就可以在「動畫不中斷」的情況下,修改動畫樣式。
HTML
<div id="test"></div>
<button id="btn">更換動畫內容</button>
CSS
#test {
width:50px;
height:50px;
position:absolute;
top:40px;
background:#f00;
animation-name: oxxo;
animation-duration:2s;
animation-iteration-count:infinite;
animation-direction:alternate;
}
@keyframes oxxo{
0%{
left:0;
background:#f00;
}
50%{
left:100px;
background:#0a0;
}
100%{
left:200px;
background:#06f;
}
}
JavaScript
var btn = document.getElementById('btn');
var keyframes = document.styleSheets[0].cssRules[1];
console.log(keyframes);
btn.addEventListener('click',function(){
console.log(keyframes.findRule('50%'));
keyframes.deleteRule('50%');
console.log(keyframes);
keyframes.appendRule('50% {left:100px; background:#000;}');
console.log(keyframes);
});
小結
當熟練了 CSS 動畫之後,其實不少原本覺得有點複雜的效果都能迎刃而解,甚至也可以因為 CSS 動畫的輔助,大幅減少 JavaScript 程式碼,也能更快速的提高網頁效能,原本自己只是想寫個紀錄的文章,結果反而把 CSS 動畫給徹底研究了一下,也有不少網頁設計改善的想法迸出,CSS 動畫真是太神奇啦!
參考
只需要看這篇就夠囉:CSS Animations Level 1