你可能不知道的 CSS3 Animation

YanceyOfficial發表於2019-04-04

過段時間要幫女孩做一些計算機視覺的東西,所以研究下動畫這一塊。前端建立動畫的方式有三種,分別是 定時器, requestAnimationFrame 以及 CSS3 Animation,而 requestAnimationFrame 的出現基本秒殺了定時器。雖然 Animation 暫無法處理如三次方緩動、指數衰減正弦曲線緩動等需要高階數學運算的動畫(貌似 CSS3 要支援三角函式了),但在絕大多數情況下依舊可以做出非常優秀的動畫效果,這裡對 Animation 各屬性做個總結。

屬性一覽

目前 animation 有下面 9 種屬性,其中第一個是後面屬性的簡寫形式。

屬性 描述
animation 下面各屬性的簡寫(除了 animation-play-state)
animation-name 指定 @keyframes 動畫的名稱
animation-duration 動畫完成一個週期的時間,預設為 0s
animation-timing-function 動畫執行的'節奏',預設是 ease
animation-delay 動畫開始播放的延遲時間,預設是 0
animation-iteration-count 動畫播放的次數,預設是 1
animation-direction 規定動畫是否在下一個週期逆向播放
animation-fill-mode 規定動畫的填充模式
animation-play-state 控制動畫的執行或暫停,預設是 running

animation-name

該屬性用於指定 @keyframes 動畫的名稱。定義關鍵幀需要使用 @keyframes 規則。樣式塊語句中可以使用 from...to 結構,也可以使用百分比來定義。

如下面這段程式碼,它在動畫開始執行時將元素放大 1.1 倍,在動畫執行到一半時將元素縮小為 0.8 倍,在動畫結束時將元素再放大為 1.1 倍,配合上後面要講到的 infinite 和貝塞爾曲線,就可以模擬出不錯的心跳動畫效果。

.heart {
  animation: heartbeat cubic-bezier(0.2, 0.73, 0.71, 0.44) infinite;
}

@keyframes heartbeat {
  0% {
    transform: scale(1.1);
  }
  50% {
    transform: scale(0.8);
  }
  100% {
    transform: scale(1.1);
  }
}
複製程式碼

HEART BEAT

animation-duration

該屬性定義一個動畫週期的時長。

  • 預設值為 0s,表示無動畫

  • 單位為秒或者毫秒,無單位值無效

animation-timing-function

該屬性用於定義動畫的'節奏',預設值為 ease.

屬性值 描述
ease 緩慢開始,緩慢結束
ease-in 先慢後快
ease-out 先快後慢
ease-in-out 以慢速開始和結束的過渡效果
linear 平滑效果
step-start 步進,忽略第一幀
step-end 步進,忽略最後一幀
step-middle 步進,從第一幀到最後一幀

不管是在 Animation 還是 Transition 裡,前 5 個應該很常見,我們可以直接在 Chrome Dev 中檢視具體的函式影像(其實這五個就是貝塞爾曲線的幾種特殊形式)。

Jietu20190404-000307@2x.jpg

step-start, step-end 和 step-middle 表示步進動畫。

其中 step-start 會忽略相應 @keyframs 規則的第一幀,而 step-end 會忽略最後一幀, step-middle 在一個週期內會從第一幀一直步進到最後一幀

看下面這個例子,當使用 step-start 時,元素的初始狀態就是 1.2 倍(對應著 25%),一個週期內步進執行 25% -> 50% -> 75% -> 100%

當使用 step-end 時,元素的初始狀態是 1.1 倍(對應著 0%),一個週期內步進執行 0% -> 25% -> 50% -> 75%

當使用 step-middle 時,元素的初始狀態就是元素的初始狀態,一個週期內步進執行 0% -> 25% -> 50% -> 75% -> 100%

@keyframes someEffect {
  0% {
    transform: scale(1.1);
  }
  25% {
    transform: scale(1.2);
  }

  50% {
    transform: scale(1.3);
  }

  75% {
    transform: scale(1.4);
  }

  100% {
    transform: scale(1.5);
  }
}
複製程式碼

此外該屬性還有三個內建函式,分別是 cubic-bezier(), steps() 以及 frames()。

這裡簡單談一談 steps()。如果你用過 Twitter,你應該知道它的點贊效果很酷,可以看下圖。這個效果完全可以用 steps() 模擬出來,我的部落格中點贊模組也有這個效果,不過是用的 box-shadow,你可以看原始碼 Like Component

1_HJHfcRwn33XU4omMogi6PQ (1).gif

首先你要下載下面這張圖片,可以看到它是一張 未點贊狀態 -> 生成禮花 -> 禮花消失 -> 點贊狀態 的雪碧圖。

1_MTZW1G1mE7LSX1CnhTYeHA.png

在初始化時,我們通過 background-position: leftbackground-size: 2900% 將初始背景定位到上圖中第一個灰色的 icon.

接著我們建立一個 @keyframes,用於在一個動畫週期內將上面的圖片從左到右一次性走完。

為了每一幀都會“跳”到下一個 icon,而不是平滑的移動。我們使用 steps() 函式,因為共有 29 張小 icon,其中預設是第一個,所以傳遞引數 28.

在點選背景圖片時,將 is_animating 新增到 heart 元素上,即可實現點贊禮花效果。

<div class="heart"></div>
複製程式碼
.heart {
  cursor: pointer;
  height: 50px;
  width: 50px;
  background-image: url('heart-locus.png');
  background-position: left;
  background-repeat: no-repeat;
  background-size: 2900%;
}

.is_animating {
  animation-name: heart-burst;
  animation-duration: 800ms;
  animation-timing-function: steps(28);
  animation-iteration-count: 1;
}

@keyframes heart-burst {
  from {
    background-position: left;
  }
  to {
    background-position: right;
  }
}
複製程式碼
const heartDOM = document.querySelector('.heart');

heartDOM.addEventListener('click', function() {
  this.classList.toggle('is_animating');
});

heartDOM.addEventListener('animationend', function() {
  this.classList.toggle('is_animating');
});
複製程式碼

animation-delay

該屬性用於將動畫延遲執行,預設值為 0s。當該屬性的屬性值為負值會出現一些好玩的事情,我們直接看 MDN 上的定義。

定義一個負值會讓動畫立即開始。但是動畫會從它的動畫序列中某位置開始。例如,如果設定值為-1s,動畫會從它的動畫序列的第 1 秒位置處立即開始。 如果為動畫延遲指定了一個負值,但起始值是隱藏的,則從動畫應用於元素的那一刻起就獲取起始值。

我們看下面這個例子:

.cube {
  margin-bottom: 10px;
  width: 100px;
  height: 100px;
  background: #ccc;
  animation: colorChange 10s linear;
}

.has-delay {
  animation-delay: 2s;
}

.has-ng-delay {
  animation-delay: -2s;
}

@keyframes colorChange {
  20% {
    background-color: #f8e81c;
  }

  40% {
    background-color: #d0011b;
  }

  60% {
    background-color: #7ed321;
  }

  80% {
    background-color: #509ce3;
  }

  100% {
    background-color: #ccc;
  }
}
複製程式碼

html 結構如下:

<div class="cube"></div>
<div class="cube has-ng-delay"></div>
<div class="cube has-delay"></div>
複製程式碼

這個例子中,我們將 cube 的動畫總時長設為 10s,且初始顏色都是灰色。不同的是,第一個 cube 正常執行,第二個 cube "延遲" -2s 執行,第三個 cube 延遲 2s 執行。

根據下面這張圖可以看出,第二個 cube 雖然是負數,但從動畫一開始就會執行,只不過它是從第二幀開始的。

這是因為定義在 @keyframes 中的動畫需要執行 animation-time 時間長度。animation-delay 為正數的時候,動畫就要要延遲開始,animation-time 都還沒有開始計算,正數的 delay 不會被計算到 animation-time 中,因此我們看到的動畫就是從第一幀開始的;animation-delay 為負數的時候,意味著動畫是提前開始的,animation-time 已經開始計算了,負數的 delay 是被算入 animation-time 中的,所以我們看到的動畫是從某一幀開始的。

xx

所以我們可以嘗試寫個 loading 元件出來。原始碼貼在下面。

.loader-container {
  width: 210px;
  display: grid;
  grid-template-columns: 1fr 1fr;
  grid-template-rows: 1fr 1fr;
  grid-template-rows: 1fr 1fr;
  grid-row-gap: 10px;
  grid-column-gap: 10px;
}

.cube {
  width: 100px;
  height: 100px;
  background: #ccc;
  animation: colorChange 5s linear infinite;
}

.delay-neg-1 {
  animation-delay: -1s;
}

.delay-neg-2 {
  animation-delay: -2s;
}

.delay-neg-3 {
  animation-delay: -3s;
}

.delay-neg-4 {
  animation-delay: -4s;
}
複製程式碼

下面是 HTML

<div class="loading-container">
  <div class="cube delay-neg-1"></div>
  <div class="cube delay-neg-2"></div>
  <div class="cube delay-neg-3"></div>
  <div class="cube delay-neg-4"></div>
</div>
複製程式碼

a757cbe5b9bc4490ffd445175913401f.gif

animation-iteration-count

該屬性定義迴圈播放動畫的次數,預設值為 1

  • 不可以為負數

  • infinite 表示無限迴圈

  • 可以為小數,比如 0.5 代表播放動畫的一半即結束

animation-direction

該屬性表示動畫是否反向播放,共有 4 個值:

  • normal: 每次從 @keyframes 0% 執行到 100%,一個週期結束後立即回到 0% 的位置

  • alternate: 假設 animation-iteration-count: infinite,從 @keyframes 0% 執行到 100%後,再從 100% 的位置 回到 0%,周而復始

  • reverse: 每次從 @keyframes 100% 執行到 0%,,一個週期結束後立即回到 100% 的位置

  • alternate-reverse: 假設 animation-iteration-count: infinite,從 @keyframes 100% 執行到 0%後,再從 0% 的位置 回到 100%,周而復始

animation-fill-mode

用於設定動畫時間外的屬性,也就是說一個動畫週期開始之前或結束之後,元素的狀態應該是什麼樣的。該屬性有四個屬性值,分別是 none, forwards, backwards, both.

  • none 是預設值,表示動畫播放完成後,恢復到初始的狀態。

  • forwards 表示動畫播放完成後,保持 @keyframes 裡最後一幀的樣式。

  • backwards 表示開始播放動畫之前,元素的樣式將設定為動畫第一幀的樣式

  • both 相當於同時配置了 forwards 和 backwards。也就是說,動畫開始前,元素樣式將設定為動畫第一幀的樣式;而在動畫線束狀態,元素樣式將設定為動畫最後一幀樣式。

animation-play-state

該屬性用於讓一個動畫的暫停與啟動,有兩個屬性值,分別是 running 和 pause,當設定為 pause 時,動畫會立即會停在當前位置,當取消暫停後會在停住的位置繼續執行,而不會回到原點(或終點)重新執行。

animation

最後說一下 animation 屬性,它是上述屬性的簡寫形式,animation 屬性暫時還沒有收錄 animation-play-state,如若使用需要單獨來寫,語法如下。

animation: name duration timing-function delay iteration-count direction fill-mode;
複製程式碼

多動畫

上面我們看到了每個屬性都可以新增多個屬性值,他們之間用逗號隔開,其實就是給一個元素新增多個動畫。

參考

CSS3 動畫實踐

CSS 動畫簡介

複習 animation-delay 負值以及 delay 正值在 iOS 上的坑

前端 Talkking CSS 系列 —— 一步一步帶你認識 animation 動畫效果

CSS3 animation 屬性中的 steps 功能符深入介紹

你所不知道的 animation-fill-mode 細節

相關文章