轉自:凹凸實驗室(https://aotu.io/notes/2016/11/28/css3-animation-properties/)
本文不會詳細介紹每個 css3 animation 屬性(需要了解的同學可先移步 MDN),而是結合實際的開發經驗,介紹 css3 animation 屬性的一些使用場景及技巧。
1. animation-delay
MDN 中的介紹:
animation-delay CSS 屬性定義動畫於何時開始,即從動畫應用在元素上到動畫開始的這段時間的長度。
該屬性值默認為 0s,可為正值,也可為負值。
動畫時間軸
由於 css3 動畫沒有時間軸,animation-delay 最常見的是用於將動畫與其他動畫的執行時機錯開,將動畫落到不同的時間點,形成動畫時間軸。
.ani--first { animation-name: aniFirst; animation-duration: 2s; animation-delay: 0s; } .ani--second { animation-name: aniSecond; animation-duration: 1s; animation-delay: 2s; /* aniSecond 延遲 2s 執行*/ }
形成的時間軸如下圖所示:

輪播
css3 animation 亦可實現一些 js 的效果,例如利用 animation-delay 可以實現一個簡單的輪播。以下是一個三屏輪播的例子。
.slider__item { animation: ani 6s infinite linear both; @for $i from 1 to 4 { &:nth-child(#{$i}) { animation-delay: (-1+$i)*2s; } } } @keyframes ani { 0%, 33.33% {opacity: 1; visibility: visible;} 33.34%, 100% {opacity: 0; visibility: hidden;} }
完整代碼:
html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> <link rel="stylesheet" type="text/css" href="css/style.css"> </head> <body> <div class="slider"> <img src="http://jdc.jd.com/img/500x300?color=6190e8&text=slider1&textColor=ffffff" class="slider__item" /> <img src="http://jdc.jd.com/img/500x300?color=2ebaae&text=slider2&textColor=ffffff" class="slider__item" /> <img src="http://jdc.jd.com/img/500x300?color=3d5a92&text=slider3&textColor=ffffff" class="slider__item" /> </div> </body> </html>
css
.slider { position: relative; width: 500px; height: 300px; } .slider:hover .slider__item { animation-play-state: paused; } .slider__item { position: absolute; width: 100%; height: 100%; left: 0; top: 0; opacity: 0; animation: ani 6s infinite linear both; } .slider__item:nth-child(1) { animation-delay: 0s; } .slider__item:nth-child(2) { animation-delay: 2s; } .slider__item:nth-child(3) { animation-delay: 4s; } @keyframes ani { 0%, 33.33% { opacity: 1; visibility: visible; } 33.34%, 100% { opacity: 0; visibility: hidden; } }
序列動畫
多個元素使用相同的動畫效果時,將動畫執行時機依次錯開,可形成整齊有序的序列動畫效果。
@for $i from 1 to 6 { .list__item:nth-child(#{$i}) { animation-delay: (-1+$i)*0.1s; /*計算每個元素的 animation-delay */ } }
完整代碼
html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> <link rel="stylesheet" type="text/css" href="css/index1.css"> </head> <body> <div class="list"> <div class="list__item"></div> <div class="list__item"></div> <div class="list__item"></div> <div class="list__item"></div> <div class="list__item"></div> </div> </body> </html>
css
.list__item { width: 50px; height: 50px; background: #6180e9; margin-right: 10px; float: left; animation: listAni 1s ease both; } @keyframes listAni { 0% { transform: scale(0); } 100% { transform: scale(1); } } .list__item:nth-child(1) { animation-delay: 0s; } .list__item:nth-child(2) { animation-delay: 0.2s; } .list__item:nth-child(3) { animation-delay: 0.4s; } .list__item:nth-child(4) { animation-delay: 0.6s; } .list__item:nth-child(5) { animation-delay: 0.8s; }
以筆者開發的京東2017海外招聘項目為例,第二屏的菜單和第三屏的時間軸的進退場動畫都運用了序列動畫。下圖展示第三屏時間軸的進場效果,有興趣的同學亦可掃碼觀看完整案例。

無限循環的序列動畫
animation-delay 可為負值。負值會讓動畫從它的動畫序列中某位置立即開始。 巧用這個負值,可以解決實際開發中的一些問題。
如若上述的序列動畫要進行無限循環,單純將 animation-iteration-count 設置為 infinite,動畫開始時會有延遲。此時,將 animation-delay 設置為負值,提前動畫開始執行的時機,當用戶看到動畫時,動畫便已經處於進行中的狀態。
@for $i from 1 to 6 { .list__item:nth-child(#{$i}) { animation-delay: -$i*0.1s; /* animation-delay 為負值*/ } }
完整代碼
html同時
css
@charset "UTF-8"; .list__item { width: 50px; height: 50px; background: #6180e9; margin-right: 10px; float: left; animation: listAni 0.5s ease both alternate infinite; } @keyframes listAni { 0% { transform: scale(0); } 100% { transform: scale(1); } } .list__item:nth-child(1) { animation-delay: -0.1s; /* animation-delay 為負值*/ } .list__item:nth-child(2) { animation-delay: -0.2s; /* animation-delay 為負值*/ } .list__item:nth-child(3) { animation-delay: -0.3s; /* animation-delay 為負值*/ } .list__item:nth-child(4) { animation-delay: -0.4s; /* animation-delay 為負值*/ } .list__item:nth-child(5) { animation-delay: -0.5s; /* animation-delay 為負值*/ }
調試動畫
將 animation-play-state 設置為 paused,animation-delay 設置成不同的負值,可以查看動畫在不同幀時的狀態,便於進行動畫調試。
.list__item { animation: listAni 0.5s linear both alternate infinite; animation-play-state: paused; } @for $i from 1 to 6 { .list--first .list__item:nth-child(#{$i}) { animation-delay: -$i*0.1s; } } @for $i from 1 to 6 { .list--second .list__item:nth-child(#{$i}) { animation-delay: (-2-$i)*0.1s; } } @for $i from 1 to 6 { .list--third .list__item:nth-child(#{$i}) { animation-delay: (-4-$i)*0.1s; } }
完整代碼
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> <link rel="stylesheet" type="text/css" href="css/index1.css"> </head> <body> <div class="list list--first"> <div class="list__item"></div> <div class="list__item"></div> <div class="list__item"></div> <div class="list__item"></div> <div class="list__item"></div> </div> <div class="list list--second"> <div class="list__item"></div> <div class="list__item"></div> <div class="list__item"></div> <div class="list__item"></div> <div class="list__item"></div> </div> <div class="list list--third"> <div class="list__item"></div> <div class="list__item"></div> <div class="list__item"></div> <div class="list__item"></div> <div class="list__item"></div> </div> </body> </html>
scss
.list { overflow: hidden; margin-bottom: 10px; &__item { width: 50px; height: 50px; background: #6180e9; margin-right: 10px; float: left; animation: listAni 0.5s linear both alternate infinite; animation-play-state: paused; } } @keyframes listAni { 0% {transform: scale(0); } 100% {transform: scale(1); } } @for $i from 1 to 6 { .list--first .list__item:nth-child(#{$i}) { animation-delay: -$i*0.1s; } } @for $i from 1 to 6 { .list--second .list__item:nth-child(#{$i}) { animation-delay: (-2-$i)*0.1s; } } @for $i from 1 to 6 { .list--third .list__item:nth-child(#{$i}) { animation-delay: (-4-$i)*0.1s; } }
2. animation-fill-mode
MDN 中的介紹:
animation-fill-mode 這個 CSS 屬性用來指定在動畫執行之前和之后如何給動畫的目標應用樣式。
animation-fill-mode 應該算是 animation 屬性里比較難上手的一個,但它的作用卻很大。
保持結束狀態
“動畫結束后,突然跳回第一幀!” 很多剛接觸 css3 動畫的同學,都是在這個場景下,接觸了 animation-fill-mode 屬性。將 animation-fill-mode 設置為 forwards,動畫執行結束后保持最后一幀的樣式。
.ani-area__item--forwards { animation: ani 1s ease; animation-fill-mode: forwards; }
完整代碼
html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> <link rel="stylesheet" type="text/css" href="css/index2.css"> </head> <body> <div class="ani-area"> <div class="ani-area__item ani-area__item--forwards"></div> <div class="ani-area__item ani-area__item--none"></div> </div> </body> </html>
scss
.ani-area { &__item { width: 50px; height: 50px; background: #6180e9; margin-right: 10px; float: left; animation: ani 1s ease; &--forwards { animation-fill-mode: forwards; } &--none { animation-fill-mode: none; } } } @keyframes ani { 0% { opacity: 0 } 100% { opacity: 0.5 } }
開始前狀態
開發動畫時,我們都是先根據視覺稿做好構建,再來給元素加動畫的。如上文所述,可通過 animation-delay 來延遲的動畫的執行。而在執行前,元素往往需要先隱藏(translate 定位到視窗外 / opacity 設置為 0 / scale 設置為 0 等)。若將隱藏元素的樣式直接應用到元素上,一來不利於構建,二來對於不支持動畫的瀏覽器來說,只會呈現一片空白。此時,animation-fill-mode 的 backwards 屬性值便派上用場。
對於 backwards 的解釋,筆者見過不少文章的說法都有不妥之處,認為 backwards 與 forwards 相反,表示動畫執行結束后保持第一幀的樣式。實則不然,我們看下 w3c 的解釋:
backwards:在 animation-delay 所指定的一段時間內,在動畫顯示之前,應用開始屬性值(在第一個關鍵幀中定義)。
換句話說,backwards 作用的是 animation-delay 的時間段,應用第一個關鍵幀的樣式。
.ani-area__item--backwards { animation: ani 1s 1s ease; animation-fill-mode: backwards; }
完整代碼
html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> <link rel="stylesheet" type="text/css" href="css/index2.css"> </head> <body> <div class="ani-area"> <div class="ani-area__item ani-area__item--backwards">1s后出現</div> <div class="ani-area__item ani-area__item--none">1s后出現</div> </div> </body> </html>
scss
.ani-area { &__item { width: 100px; height: 50px; color: #fff; line-height: 50px; text-align: center; background: #6180e9; margin-right: 10px; float: left; animation: ani 1s 1s ease; &--backwards { animation-fill-mode: backwards; } &--none { animation-fill-mode: none; } } } @keyframes ani { 0% { opacity: 0 } 100% { opacity: 1 } }
當然,動畫的第一幀和最后一幀的計算還受 animation-direction 和 animation-iteration-count 的影響,MDN 中有詳細解釋:
forwarls:

backwards:

3. animation-direction
既然上表中涉及了 animation-direction 屬性,那我們就順着來研究一下它。
MDN 中的介紹:
animation-direction CSS 屬性指示動畫是否反向播放。
進/退場動畫復用
動畫元素有進場動畫,往往也會需要退場動畫。比較常見的做法,退場時使用與進場動畫反向的動畫。animation-direction 的 reverse 屬性值可簡單實現反向動畫。
先看MDN 中的介紹:
reverse:反向運行動畫,每周期結束動畫由尾到頭運行。
.on { .ani--translate { animation: aniTranslate 1s ease forwards; } } .off { .ani--translate { animation: aniTranslate 1s ease forwards reverse; } } @keyframes aniTranslate { 0% { transform: translateY(300px) } 100% { transform: translateY(0) } }
$wrap.removeClass('on'); $wrap.innerWidth($wrap.innerWidth); /* 使用 reflow 重新觸發一下 animation */ $wrap.addClass('off');
完整實例代碼:
html:
<div class="ani-wrap on J_wrap"> <div class="ani ani--opacity"></div> <div class="ani ani--scale"></div> <div class="ani ani--translate"></div> </div> <a href="javascript:;" class="btn J_btn">退場</a>
scss
.btn { display: block; width: 100px; height: 30px; line-height: 30px; text-align: center; color: #6180e9; border: 1px solid #6180e9; float: left; } .ani { width: 50px; height: 50px; background: #6180e9; margin-right: 10px; float: left; } .on { .ani { &--opacity { animation: aniOpacity 1s ease forwards; } &--scale { animation: aniScale 1s ease forwards; } &--translate { animation: aniTranslate 1s ease forwards; } } } .off { .ani { &--opacity { animation: aniOpacity 1s ease forwards reverse; } &--scale { animation: aniScale 1s ease forwards reverse; } &--translate { animation: aniTranslate 1s ease forwards reverse; } } } @keyframes aniOpacity { 0% { opacity: 0 } 100% { opacity: 1 } } @keyframes aniScale { 0% { transform: scale(0) } 100% { transform: scale(1) } } @keyframes aniTranslate { 0% { transform: translateY(300px) } 100% { transform: translateY(0) } }
js
var $btn = $('.btn'), $wrap = $('.J_wrap'); $btn.on('click', function(e) { $wrap.removeClass('on').innerWidth($wrap.innerWidth).addClass('off'); })
當然,上述例子為了演示方便,只是簡單做了只有兩幀的動畫,這種效果用 transition 同樣可以實現。
4. animation-play-state
MDN 中的介紹:
animation-play-state CSS 屬性定義一個動畫是否運行或者暫停。
翻頁動畫控制
在做翻頁 h5 時,需要對動畫的播放進行控制。只有當用戶進入當前屏時,動畫才開始播放。通常我們會給當前屏加上一個 acitve 類,用來給元素添加動畫:
.active .ele {
animation: ani 1s ease;
}
或者如上文“進/退場動畫復用”中的例子,分別用 on 和 off 控制進/退場動畫。這都是常見的思路。
如果是不需要重復觸發的動畫,用 animation-play-state 同樣可以實現動畫的控制。動畫屬性直接添加到元素上, animation-play-state 默認設置為 paused,當進入當前屏時,將 animation-play-state 設置為 running 即可。
.ani { animation: ani1 1s ease; animation-play-state: paused; /* animation-play-state 默認設置為 paused */ } .active .ani { animation-play-state: running; /* 進入當前屏,animation-play-state 設置為 running */ }
完整實例代碼:
html
<div class="page-wrap J_wraper"> <div class="page page--first active"> <div class="ani ani--first"> </div> </div> <div class="page page--second"> <div class="ani ani--second"> </div> </div> <div class="page page--third"> <div class="ani ani--third"></div> </div> </div>
scss
.page-wrap{ position: relative; width: 320px; height: 504px; overflow: hidden; background: #eee; } .page { position: absolute; width: 100%; height: 100%; top: 0; left: 0; &--first { background: #ddd; } &--second { background: #2ebaae; } &--third { background: #3d5a92; } &.active { z-index: 1; } } .ani { width: 100px; height: 50px; color: #fff; line-height: 50px; text-align: center; background: #6190e8; margin: 110px; float: left; &--first { animation: ani1 2s ease both; animation-play-state: paused; -webkit-animation: ani1 2s ease both; -webkit-animation-play-state: paused; } &--second { animation: ani2 2s ease both; animation-play-state: paused; -webkit-animation: ani2 2s ease both; -webkit-animation-play-state: paused; } &--third { animation: ani3 2s ease both; animation-play-state: paused; -webkit-animation: ani3 2s ease both; -webkit-animation-play-state: paused; } } @keyframes ani1 { 0% { opacity: 0 } 100% { opacity: 1 } } @-webkit-keyframes ani1 { 0% { opacity: 0 } 100% { opacity: 1 } } @keyframes ani2 { 0% { transform: scale(0) } 100% { transform: scale(1) } } @-webkit-keyframes ani2 { 0% { -webkit-transform: scale(0) } 100% { -webkit-transform: scale(1) } } @keyframes ani3 { 0% { transform: translateY(1000px) } 100% { transform: translateY(0) } } @-webkit-keyframes ani3 { 0% { -webkit-transform: translateY(1000px) } 100% { -webkit-transform: translateY(0) } } .active .ani { animation-play-state: running; -webkit-animation-play-state: running; }
js
var $page = $('.J_wraper .page'); $page.on('click',function(e) { $(this).next().addClass('active'); })
輪播的交互
在前文介紹 animation-delay 時,提到了一個輪播的例子,當用戶 hover 時,輪播動畫應該暫停,用 animation-play-state 屬性便可輕松實現交互:
.slider:hover .slider__item{ animation-play-state: paused; }
5. animation-timing-function
MDN 中的介紹:
CSS animation-timing-function 屬性定義 CSS 動畫在每一動畫周期中執行的節奏。
關於 animation-timing-function,有一個特別需要注意的點,MDN 中有強調:
對於關鍵幀動畫來說,timing function 作用於一個關鍵幀周期而非整個動畫周期,即從關鍵幀開始開始,到關鍵幀結束結束。
也就是說,animation-timing-function 是作用於 @keyframes 中設置的兩個關鍵幀之間的,這一點在該屬性值為 steps() 時可明顯感知。
逐幀動畫
animation-timing-function 最讓人感到驚(beng)艷(kui)的莫過於 steps() 屬性值。利用 steps(),可以輕松實現逐幀動畫(又稱“精靈動畫”),從而告別不可控的 gif 時代。
關於逐幀動畫,筆者之前在凹凸實驗室平台已經發布過相關文章介紹,此處不再贅述,有興趣的同學可前往圍觀:《CSS3逐幀動畫》。
參考文章:
- Debugging CSS Keyframe Animations - SARAH DRASNER
- 多屏復雜動畫CSS技巧三則 - zhangxinxu
- 打造H5動感影集的愛恨情仇(動畫性能篇) -TQ
