自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

深入淺出談 CSS 動畫

開發(fā) 前端
本文將比較全面細(xì)致的梳理一下 CSS 動畫的方方面面,針對每個屬性用法的講解及進(jìn)階用法的示意,希望能成為一個比較好的從入門到進(jìn)階的教程。

本文將比較全面細(xì)致的梳理一下 CSS 動畫的方方面面,針對每個屬性用法的講解及進(jìn)階用法的示意,希望能成為一個比較好的從入門到進(jìn)階的教程。

CSS 動畫介紹及語法

首先,我們來簡單介紹一下 CSS 動畫。

最新版本的 CSS 動畫由規(guī)范 -- CSS Animations Level 1[1] 定義。

CSS 動畫用于實現(xiàn)元素從一個 CSS 樣式配置轉(zhuǎn)換到另一個 CSS 樣式配置。

動畫包括兩個部分: 描述動畫的樣式規(guī)則和用于指定動畫開始、結(jié)束以及中間點樣式的關(guān)鍵幀。

簡單來說,看下面的例子:

  1. div { 
  2.     animation: change 3s; 
  3.  
  4. @keyframes change { 
  5.     0% { 
  6.         color: #f00; 
  7.     } 
  8.     100% { 
  9.         color: #000; 
  10.     } 
  1. animation: move 1s 部分就是動畫的第一部分,用于描述動畫的各個規(guī)則;
  2. @keyframes move {} 部分就是動畫的第二部分,用于指定動畫開始、結(jié)束以及中間點樣式的關(guān)鍵幀;

一個 CSS 動畫一定要由上述兩部分組成。

CSS 動畫的語法

接下來,我們簡單看看 CSS 動畫的語法。

創(chuàng)建動畫序列,需要使用 animation 屬性或其子屬性,該屬性允許配置動畫時間、時長以及其他動畫細(xì)節(jié),但該屬性不能配置動畫的實際表現(xiàn),動畫的實際表現(xiàn)是由 @keyframes 規(guī)則實現(xiàn)。

animation 的子屬性有:

  • animation-name:指定由 @keyframes 描述的關(guān)鍵幀名稱。
  • animation-duration:設(shè)置動畫一個周期的時長。
  • animation-delay:設(shè)置延時,即從元素加載完成之后到動畫序列開始執(zhí)行的這段時間。
  • animation-direction:設(shè)置動畫在每次運行完后是反向運行還是重新回到開始位置重復(fù)運行。
  • animation-iteration-count:設(shè)置動畫重復(fù)次數(shù), 可以指定 infinite 無限次重復(fù)動畫
  • animation-play-state:允許暫停和恢復(fù)動畫。
  • animation-timing-function:設(shè)置動畫速度, 即通過建立加速度曲線,設(shè)置動畫在關(guān)鍵幀之間是如何變化。
  • animation-fill-mode:指定動畫執(zhí)行前后如何為目標(biāo)元素應(yīng)用樣式
  • @keyframes 規(guī)則,當(dāng)然,一個動畫想要運行,還應(yīng)該包括 @keyframes 規(guī)則,在內(nèi)部設(shè)定動畫關(guān)鍵幀

其中,對于一個動畫:

  • 必須項:animation-name、animation-duration 和 @keyframes規(guī)則
  • 非必須項:animation-delay、animation-direction、animation-iteration-count、animation-play-state、animation-timing-function、animation-fill-mode,當(dāng)然不是說它們不重要,只是不設(shè)置時,它們都有默認(rèn)值

上面已經(jīng)給了一個簡單的 DEMO, 就用上述的 DEMO,看看結(jié)果:

這就是一個最基本的 CSS 動畫,本文將從 animation 的各個子屬性入手,探究 CSS 動畫的方方面面。

animation-name / animation-duration 詳解

整體而言,單個的 animation-name 和 animation-duration 沒有太多的技巧,非常好理解,放在一起。

首先介紹一下 animation-name,通過 animation-name,CSS 引擎將會找到對應(yīng)的 @keyframes 規(guī)則。

當(dāng)然,它和 CSS 規(guī)則命名一樣,也存在一些騷操作。譬如,他是支持 emoji 表情的,所以代碼中的 animation-name 命名也可以這樣寫:

  1. div { 
  2.     animation: 😄 3s; 
  3.  
  4. @keyframes 😄 { 
  5.     0% { 
  6.         color: #f00; 
  7.     } 
  8.     100% { 
  9.         color: #000; 
  10.     } 

而 animation-duration 設(shè)置動畫一個周期的時長,上述 DEMO 中,就是設(shè)定動畫整體持續(xù) 3s,這個也非常好理解。

animation-delay 詳解

animation-delay 就比較有意思了,它可以設(shè)置動畫延時,即從元素加載完成之后到動畫序列開始執(zhí)行的這段時間。

簡單的一個 DEMO:

  1. <div></div> 
  2. <div></div> 
  1. div { 
  2.     width: 100px; 
  3.     height: 100px; 
  4.     background: #000; 
  5.     animation-namemove
  6.     animation-duration: 2s; 
  7.  
  8. div:nth-child(2) { 
  9.     animation-delay: 1s; 
  10. @keyframes move { 
  11.     0% { 
  12.         transform: translate(0); 
  13.     } 
  14.     100% { 
  15.         transform: translate(200px); 
  16.     } 

比較下列兩個動畫,一個添加了 animation-delay,一個沒有,非常直觀:

上述第二個 div,關(guān)于 animation 屬性,也可以簡寫為 animation: move 2s 1s,第一個時間值表示持續(xù)時間,第二個時間值表示延遲時間。

animation-delay 可以為負(fù)值

關(guān)于 animation-delay,最有意思的技巧在于,它可以是負(fù)數(shù)。也就是說,雖然屬性名是動畫延遲時間,但是運用了負(fù)數(shù)之后,動畫可以提前進(jìn)行。

假設(shè)我們要實現(xiàn)這樣一個 loading 動畫效果:

圖片

有幾種思路:

  1. 初始 3 個球的位置就是間隔 120°,同時開始旋轉(zhuǎn),但是這樣代碼量會稍微多一點
  2. 另外一種思路,同一個動畫,3 個元素的其中兩個延遲整個動畫的 1/3,2/3 時間出發(fā)

方案 2 的核心偽代碼如下:

  1. .item:nth-child(1) { 
  2.     animation: rotate 3s infinite linear; 
  3. .item:nth-child(2) { 
  4.     animation: rotate 3s infinite 1s linear; 
  5. .item:nth-child(3) { 
  6.     animation: rotate 3s infinite 2s linear; 

但是,在動畫的前 2s,另外兩個元素是不會動的,只有 2s 過后,整個動畫才是我們想要的:

此時,我們可以讓第 2、3 個元素的延遲時間,改為負(fù)值,這樣可以讓動畫延遲進(jìn)行 -1s、-2s,也就是提前進(jìn)行 1s、2s:

  1. .item:nth-child(1) { 
  2.     animation: rotate 3s infinite linear; 
  3. .item:nth-child(2) { 
  4.     animation: rotate 3s infinite -1s linear; 
  5. .item:nth-child(3) { 
  6.     animation: rotate 3s infinite -2s linear; 

這樣,每個元素都無需等待,直接就是運動狀態(tài)中的,并且元素間隔位置是我們想要的結(jié)果:

利用 animation-duration 和 animation-delay 構(gòu)建隨機效果

還有一個有意思的小技巧。

同一個動畫,我們利用一定范圍內(nèi)隨機的 animation-duration 和一定范圍內(nèi)隨機的 animation-delay,可以有效的構(gòu)建更為隨機的動畫效果,讓動畫更加的自然。

我在下述兩個純 CSS 動畫中,都使用了這樣的技巧:

純 CSS 實現(xiàn)華為充電動畫[2]:

純 CSS 實現(xiàn)華為充電動畫

純 CSS 實現(xiàn)火焰動畫[3]:

純 CSS 實現(xiàn)火焰動畫

以純 CSS 實現(xiàn)華為充電動畫為例子,簡單講解一下。

仔細(xì)觀察這一部分,上升的一個一個圓球,拋去這里的一些融合效果,只關(guān)注不斷上升的圓球,看著像是沒有什么規(guī)律可言:

圖片

 我們來模擬一下,如果是使用 10 個 animation-duration 和 animation-delay 都一致的圓的話,核心偽代碼:

  1. <ul> 
  2.     <li></li> 
  3.     <!--共 10 個...-->  
  4.     <li></li> 
  5. </ul> 

  1. ul { 
  2.     display: flex; 
  3.     flex-wrap: nowrap; 
  4.     gap: 5px; 
  5. li { 
  6.     background: #000; 
  7.     animation: move 3s infinite 1s linear; 
  8. @keyframes move { 
  9.     0% { 
  10.         transform: translate(0, 0); 
  11.     } 
  12.     100% { 
  13.         transform: translate(0, -100px); 
  14.     } 

 這樣,小球的運動會是這樣的整齊劃一:

要讓小球的運動顯得非常的隨機,只需要讓 animation-duration 和 animation-delay 都在一定范圍內(nèi)浮動即可,改造下 CSS:

  1. @for $i from 1 to 11 { 
  2.     li:nth-child(#{$i}) { 
  3.         animation-duration: #{random(2000)/1000 + 2}s; 
  4.         animation-delay: #{random(1000)/1000 + 1}s; 
  5.     } 

我們利用 SASS 的循環(huán)和 random() 函數(shù),讓 animation-duration 在 2-4 秒范圍內(nèi)隨機,讓 animation-delay 在 1-2 秒范圍內(nèi)隨機,這樣,我們就可以得到非常自然且不同的上升動畫效果,基本不會出現(xiàn)重復(fù)的畫面,很好的模擬了隨機效果:

CodePen Demo -- 利用范圍隨機 animation-duration 和 animation-delay 實現(xiàn)隨機動畫效果[4]

animation-timing-function 緩動函數(shù)

緩動函數(shù)在動畫中非常重要,它定義了動畫在每一動畫周期中執(zhí)行的節(jié)奏。

緩動主要分為兩類:

  1. cubic-bezier-timing-function 三次貝塞爾曲線緩動函數(shù)
  2. step-timing-function 步驟緩動函數(shù)(這個翻譯是我自己翻的,可能有點奇怪)

三次貝塞爾曲線緩動函數(shù)

首先先看看三次貝塞爾曲線緩動函數(shù)。在 CSS 中,支持一些緩動函數(shù)關(guān)鍵字。

  1. /* Keyword values */ 
  2. animation-timing-function: ease;  // 動畫以低速開始,然后加快,在結(jié)束前變慢 
  3. animation-timing-function: ease-in;  // 動畫以低速開始 
  4. animation-timing-function: ease-out; // 動畫以低速結(jié)束 
  5. animation-timing-function: ease-in-out; // 動畫以低速開始和結(jié)束 
  6. animation-timing-function: linear; // 勻速,動畫從頭到尾的速度是相同的 

關(guān)于它們之間的效果對比:

除了 CSS 支持的這 5 個關(guān)鍵字,我們還可以使用 cubic-bezier() 方法自定義三次貝塞爾曲線:

  1. animation-timing-function: cubic-bezier(0.1, 0.7, 1.0, 0.1); 

這里有個非常好用的網(wǎng)站 -- cubic-bezier[5] 用于創(chuàng)建和調(diào)試生成不同的貝塞爾曲線參數(shù)。

三次貝塞爾曲線緩動對動畫的影響

關(guān)于緩動函數(shù)對動畫的影響,這里有一個非常好的示例。這里我們使用了純 CSS 實現(xiàn)了一個鐘的效果,對于其中的動畫的運動,如果是 animation-timing-function: linear,效果如下:

而如果我們我把緩動函數(shù)替換一下,變成 animation-timing-function: cubic-bezier(1,-0.21,.85,1.29),它的曲線對應(yīng)如下:

整個鐘的動畫律動效果將變成這樣,完全不一樣的感覺:

CodePen Demo - 緩動不同效果不同[6]

對于許多精益求精的動畫,在設(shè)計中其實都考慮到了緩動函數(shù)。我很久之前看到過一篇《基于物理學(xué)的動畫用戶體驗設(shè)計》,可惜如今已經(jīng)無法找到原文。其中傳達(dá)出的一些概念是,動畫的設(shè)計依據(jù)實際在生活中的表現(xiàn)去考量。

譬如 linear 這個緩動,實際應(yīng)用于某些動畫中會顯得很不自然,因為由于空氣阻力的存在,程序模擬的勻速直線運動在現(xiàn)實生活中是很難實現(xiàn)的。因此對于這樣一個用戶平時很少感知到的運動是很難建立信任感的。這樣的勻速直線運動也是我們在進(jìn)行動效設(shè)計時需要極力避免的。

步驟緩動函數(shù)

接下來再講講步驟緩動函數(shù)。在 CSS 的 animation-timing-function 中,它有如下幾種表現(xiàn)形態(tài):

  1.     /* Keyword values */ 
  2.     animation-timing-function: step-start; 
  3.     animation-timing-function: step-end
  4.  
  5.     /* Function values */ 
  6.     animation-timing-function: steps(6, start) 
  7.     animation-timing-function: steps(4, end); 

在 CSS 中,使用步驟緩動函數(shù)最多的,就是利用其來實現(xiàn)逐幀動畫。假設(shè)我們有這樣一張圖(圖片大小為 1536 x 256,圖片來源于網(wǎng)絡(luò)):

可以發(fā)現(xiàn)它其實是一個人物行進(jìn)過程中的 6 種狀態(tài),或者可以為 6 幀,我們利用 animation-timing-function: steps(6) 可以將其用一個 CSS 動畫串聯(lián)起來,代碼非常的簡單:

  1. <div class="box"></div> 
  1. .box { 
  2.   width: 256px; 
  3.   height: 256px; 
  4.   background: url('https://github.com/iamalperen/playground/blob/main/SpriteSheetAnimation/sprite.png?raw=true'); 
  5.   animation: sprite .6s steps(6, end) infinite; 
  6. @keyframes sprite { 
  7.   0% {  
  8.     background-position: 0 0; 
  9.   } 
  10.   100% {  
  11.     background-position: -1536px 0; 
  12.   } 

簡單解釋一下上述代碼,首先要知道,剛好 256 x 6 = 1536,所以上述圖片其實可以剛好均分為 6 段:

  1. 我們設(shè)定了一個大小都為 256px 的 div,給這個 div 賦予了一個 animation: sprite .6s steps(6) infinite 動畫;
  2. 其中 steps(6) 的意思就是將設(shè)定的 @keyframes 動畫分為 6 次(6幀)執(zhí)行,而整體的動畫時間是 0.6s,所以每一幀的停頓時長為 0.1s;
  3. 動畫效果是由 background-position: 0 0 到 background-position: -1536px 0,由于上述的 CSS 代碼沒有設(shè)置 background-repeat,所以其實 background-position: 0 0 是等價于 background-position: -1536px 0,就是圖片在整個動畫過程中推進(jìn)了一輪,只不過每一幀停在了特點的地方,一共 6 幀。

將上述 1、2、3,3 個步驟畫在圖上簡單示意:

從上圖可知,其實在動畫過程中,background-position 的取值其實只有 background-position: 0 0,background-position: -256px 0,background-position: -512px 0 依次類推一直到 background-position: -1536px 0,由于背景的 repeat 的特性,其實剛好回到原點,由此又重新開始新一輪同樣的動畫。

所以,整個動畫就會是這樣,每一幀停留 0.1s 后切換到下一幀(注意這里是個無限循環(huán)動畫),:

完整的代碼你可以戳這里 -- CodePen Demo -- Sprite Animation with steps() [7]

animation-duration 動畫長短對動畫的影響

在這里再插入一個小章節(jié),animation-duration 動畫長短對動畫的影響也是非常明顯的。

在上述代碼的基礎(chǔ)上,我們再修改 animation-duration,縮短每一幀的時間就可以讓步行的效果變成跑步的效果,同理,也可以增加每一幀的停留時間。讓每一步變得緩慢,就像是在步行一樣。

需要提出的是,上文說的每一幀,和瀏覽器渲染過程中的 FPS 的每一幀不是同一個概念。

看看效果,設(shè)置不同的 animation-duration 的效果(這里是 0.6s -> 0.2s),GIF 錄屏丟失了一些關(guān)鍵幀,實際效果會更好點:

當(dāng)然,在 steps() 中,還有 steps(6, start) 和 steps(6, end) 的差異,也就是其中關(guān)鍵字 start 和 end 的差異。對于上述的無限動畫而言,其實基本是可以忽略不計的,它主要是控制動畫第一幀的開始和持續(xù)時長,比較小的一個知識點但是想講明白需要比較長的篇幅,限于本文的內(nèi)容,在這里不做展開,讀者可以自行了解。

同個動畫效果的補間動畫和逐幀動畫演繹對比

上述的三次貝塞爾曲線緩動和步驟緩動,其實就是對應(yīng)的補間動畫和逐幀動畫。

對于同個動畫而言,有的時候兩種緩動都是適用的。我們在具體使用的時候需要具體分析選取。

假設(shè)我們用 CSS 實現(xiàn)了這樣一個圖形:

現(xiàn)在想利用這個圖形制作一個 Loading 效果,如果利用補間動畫,也就是三次貝塞爾曲線緩動的話,讓它旋轉(zhuǎn)起來,得到的效果非常的一般:

  1. .g-container{ 
  2.     animation: rotate 2s linear infinite; 
  3. @keyframes rotate { 
  4.     0% { 
  5.         transform: rotate(0); 
  6.     } 
  7.     100% { 
  8.         transform: rotate(360deg); 
  9.     } 

動畫效果如下:

但是如果這里,我們將補間動畫換成逐幀動畫,因為有 20 個點,所以設(shè)置成 steps(20),再看看效果,會得到完全不一樣的感覺:

  1. .g-container{ 
  2.     animation: rotate 2s steps(20) infinite; 
  3. @keyframes rotate { 
  4.     0% { 
  5.         transform: rotate(0); 
  6.     } 
  7.     100% { 
  8.         transform: rotate(360deg); 
  9.     } 

動畫效果如下:

整個 loading 的圈圈看上去好像也在旋轉(zhuǎn),實際上只是 20 幀關(guān)鍵幀在切換,整體的效果感覺更適合 Loading 的效果。

因此,兩種動畫效果都是很有必要掌握的,在實際使用的時候靈活嘗試,選擇更適合的。

上述 DEMO 效果完整的代碼:CodePen Demo -- Scale Loading steps vs linear[8]

animation-play-state

接下來,我們講講 animation-play-state,顧名思義,它可以控制動畫的狀態(tài) -- 運行或者暫停。類似于視頻播放器的開始和暫停。是 CSS 動畫中有限的控制動畫狀態(tài)的手段之一。

它的取值只有兩個(默認(rèn)為 running):

  1.     animation-play-state: paused | running; 

使用起來也非常簡單,看下面這個例子,我們在 hover 按鈕的時候,實現(xiàn)動畫的暫停:

  1. <div class="btn stop">stop</div> 
  2. <div class="animation"></div> 
  1. .animation { 
  2.     width: 100px; 
  3.     height: 100px; 
  4.     background: deeppink; 
  5.     animation: move 2s linear infinite alternate; 
  6.  
  7. @keyframes move { 
  8.     100% { 
  9.         transform: translate(100px, 0); 
  10.     } 
  11.  
  12. .stop:hover ~ .animation { 
  13.     animation-play-state: paused; 

一個簡單的 CSS 動畫,但是當(dāng)我們 hover 按鈕的時候,給動畫元素添加上 animation-play-state: paused:

animation-play-state 小技巧,默認(rèn)暫停,點擊運行

正常而言,按照正常思路使用 animation-play-state: paused 是非常簡單的。

但是,如果我們想創(chuàng)造一些有意思的 CSS 動畫效果,不如反其道而行之。

我們都知道,正常情況下,動畫應(yīng)該是運行狀態(tài),那如果我們將一些動畫的默認(rèn)狀態(tài)設(shè)置為暫停,只有當(dāng)鼠標(biāo)點擊或者 hover 的時候,才設(shè)置其 animation-play-state: running,這樣就可以得到很多有趣的 CSS 效果。

看個倒酒的例子,這是一個純 CSS 動畫,但是默認(rèn)狀態(tài)下,動畫處于 animation-play-state: paused,也就是暫停狀態(tài),只有當(dāng)鼠標(biāo)點擊杯子的時,才設(shè)置 animation-play-state: running,讓酒倒下,

完整的 DEMO 你可以戳這里:CodePen Demo -- CSS Beer![9]

在非常多 Web 創(chuàng)意交互動畫我們都可以看到這個技巧的身影。

  1. 頁面 render 后,無任何操作,動畫不會開始。只有當(dāng)鼠標(biāo)對元素進(jìn)行 click ,通過觸發(fā)元素的 :active 偽類效果的時候,賦予動畫 animation-play-state: running,動畫才開始進(jìn)行;
  2. 動畫進(jìn)行到任意時刻,鼠標(biāo)停止點擊,偽類消失,則動畫停止;

animation-fill-mode 控制元素在各個階段的狀態(tài)

下一個屬性 animation-fill-mode,很多人會誤認(rèn)為它只是用于控制元素在動畫結(jié)束后是否復(fù)位。這個其實是不準(zhǔn)確的,不全面的。

看看它的取值:

  1.     // 默認(rèn)值,當(dāng)動畫未執(zhí)行時,動畫將不會將任何樣式應(yīng)用于目標(biāo),而是使用賦予給該元素的 CSS 規(guī)則來顯示該元素的狀態(tài) 
  2.     animation-fill-mode: none; 
  3.     // 動畫將在應(yīng)用于目標(biāo)時立即應(yīng)用第一個關(guān)鍵幀中定義的值,并在 `animation-delay` 期間保留此值, 
  4.     animation-fill-mode: backwards;  
  5.     // 目標(biāo)將保留由執(zhí)行期間遇到的最后一個關(guān)鍵幀計算值。 最后一個關(guān)鍵幀取決于 `animation-direction` 和 `animation-iteration-count
  6.     animation-fill-mode: forwards;     
  7.     // 動畫將遵循 `forwards` 和 `backwards` 的規(guī)則,從而在兩個方向上擴(kuò)展動畫屬性 
  8.     animation-fill-mode: both;  

對于 animation-fill-mode 的解讀,我在 Segment Fault 上的一個問答中(SF - 如何理解 animation-fill-mode[10])看到了 4 副很好的解讀圖,這里借用一下:

假設(shè) HTML 如下:

  1. <div class="box"></div> 

CSS如下:

  1. .box{ 
  2.     transform: translateY(0); 
  3. .box.on
  4.     animation: move 1s; 
  5.  
  6. @keyframes move
  7.     from{transform: translateY(-50px)} 
  8.     to  {transform: translateY( 50px)} 

使用圖片來表示 translateY 的值與 時間 的關(guān)系:

  • 橫軸為表示 時間,為 0 時表示動畫開始的時間,也就是向 box 加上 on 類名的時間,橫軸一格表示 0.5s
  • 縱軸表示 translateY 的值,為 0 時表示 translateY 的值為 0,縱軸一格表示 50px

animation-fill-mode: none 表現(xiàn)如圖:

一句話總結(jié),元素在動畫時間之外,樣式只受到它的 CSS 規(guī)則限制,與 @keyframes 內(nèi)的關(guān)鍵幀定義無關(guān)。

animation-fill-mode: backwards 表現(xiàn)如圖:

一句話總結(jié),元素在動畫開始之前(包含未觸發(fā)動畫階段及 animation-delay 期間)的樣式為動畫運行時的第一幀,而動畫結(jié)束后的樣式則恢復(fù)為 CSS 規(guī)則設(shè)定的樣式。

animation-fill-mode: forwards 表現(xiàn)如圖:

一句話總結(jié),元素在動畫開始之前的樣式為 CSS 規(guī)則設(shè)定的樣式,而動畫結(jié)束后的樣式則表現(xiàn)為由執(zhí)行期間遇到的最后一個關(guān)鍵幀計算值(也就是停在最后一幀)。

animation-fill-mode: both 表現(xiàn)如圖: 

一句話總結(jié),綜合了 animation-fill-mode: backwards 和 animation-fill-mode: forwards 的設(shè)定。動畫開始前的樣式為動畫運行時的第一幀,動畫結(jié)束后停在最后一幀。

animation-iteration-count/animation-direction 動畫循環(huán)次數(shù)和方向

講到了 animation-fill-mode,我們就可以順帶講講這個兩個比較好理解的屬性 -- animation-iteration-count 和 animation-direction

  • animation-iteration-count 控制動畫運行的次數(shù),可以是數(shù)字或者 infinite,注意,數(shù)字可以是小數(shù)
  • animation-direction 控制動畫的方向,正向、反向、正向交替與反向交替

在上面講述 animation-fill-mode 時,我使用了動畫運行時的第一幀替代了@keyframes 中定義的第一幀這種說法,因為動畫運行的第一幀和最后一幀的實際狀態(tài)還會受到動畫運行方向 animation-direction 和 animation-iteration-count 的影響。

在 CSS 動畫中,由 animation-iteration-count 和 animation-direction 共同決定動畫運行時的第一幀和最后一幀的狀態(tài)。

  1. 動畫運行的第一幀由 animation-direction 決定
  2. 動畫運行的最后一幀由 animation-iteration-count 和 animation-direction 決定

動畫的最后一幀,也就是動畫運行的最終狀態(tài),并且我們可以利用 animation-fill-mode: forwards 讓動畫在結(jié)束后停留在這一幀,這個還是比較好理解的,但是 animation-fill-mode: backwards 和 animation-direction 的關(guān)系很容易弄不清楚,這里簡答講解下。

設(shè)置一個 100px x 100px 的滑塊,在一個 400px x 100px 的容器中,其代碼如下:

  1. <div class="g-father"
  2.     <div class="g-box"></div> 
  3. </div> 
  1. .g-father { 
  2.     width: 400px; 
  3.     height: 100px; 
  4.     border: 1px solid #000; 
  5. .g-box { 
  6.     width: 100px; 
  7.     height: 100px; 
  8.     background: #333; 

表現(xiàn)如下:

那么,加入 animation 之后,在不同的 animation-iteration-count 和 animation-direction 作用下,動畫的初始和結(jié)束狀態(tài)都不一樣。

如果設(shè)置了 animation-fill-mode: backwards,則元素在動畫未開始前的狀態(tài)由 animation-direction 決定:

  1. .g-box { 
  2.     ... 
  3.     animation: move 4s linear; 
  4.     animation-play-state: paused; 
  5.     transform: translate(0, 0); 
  6. @keyframes move { 
  7.     0% { 
  8.         transform: translate(100px, 0); 
  9.     } 
  10.     100% { 
  11.         transform: translate(300px, 0); 
  12.     } 

注意這里 CSS 規(guī)則中,元素沒有設(shè)置位移 transform: translate(0, 0),而在動畫中,第一個關(guān)鍵幀和最后一個關(guān)鍵的 translateX 分別是 100px、300px,配合不同的 animation-direction 初始狀態(tài)如下。

下圖假設(shè)我們設(shè)置了動畫默認(rèn)是暫停的 -- animation-play-state: paused,那么動畫在開始前的狀態(tài)為:

動畫的分治與復(fù)用

講完了每一個屬性,我們再來看看一些動畫使用過程中的細(xì)節(jié)。

看這樣一個動畫:

  1. <div></div> 
  1. div { 
  2.     width: 100px; 
  3.     height: 100px; 
  4.     background: #000; 
  5.     animation: combine 2s; 
  6. @keyframes combine { 
  7.     100% { 
  8.         transform: translate(0, 150px); 
  9.         opacity: 0; 
  10.     } 

這里我們實現(xiàn)了一個 div 塊下落動畫,下落的同時產(chǎn)生透明度的變化:

對于這樣一個多個屬性變化的動畫,它其實等價于:

  1. div { 
  2.     animation: falldown 2s, fadeIn 2s; 
  3.  
  4. @keyframes falldown { 
  5.     100% { 
  6.         transform: translate(0, 150px); 
  7.     } 
  8. @keyframes fadeIn { 
  9.     100% { 
  10.         opacity: 0; 
  11.     } 

在 CSS 動畫規(guī)則中,animation 是可以接收多個動畫的,這樣做的目的不僅僅只是為了復(fù)用,同時也是為了分治,我們對每一個屬性層面的動畫能夠有著更為精確的控制。

keyframes 規(guī)則的設(shè)定

我們經(jīng)常能夠在各種不同的 CSS 代碼見到如下兩種 CSS @keyframes 的設(shè)定:

使用百分比

  1. @keyframes fadeIn { 
  2.     0% { 
  3.         opacity: 1; 
  4.     } 
  5.     100% { 
  6.         opacity: 0; 
  7.     } 

使用 from 及 to

  1. @keyframes fadeIn { 
  2.     from { 
  3.         opacity: 1; 
  4.     } 
  5.     to { 
  6.         opacity: 0; 
  7.     } 

在 CSS 動畫 @keyframes 的定義中,from 等同于 0%,而 to 等同于 100%。

當(dāng)然,當(dāng)我們的關(guān)鍵幀不止 2 幀的時,更推薦使用百分比定義的方式。

除此之外,當(dāng)動畫的起始幀等同于 CSS 規(guī)則中賦予的值并且沒有設(shè)定 animation-fill-mode,0% 和 from 這一幀是可以刪除的。

動畫狀態(tài)的高優(yōu)先級性

我曾經(jīng)在這篇文章中 -- 深入理解 CSS(Cascading Style Sheets)中的層疊(Cascading)[11] 講過一個很有意思的 CSS 現(xiàn)象。

這也是很多人對 CSS 優(yōu)先級的一個認(rèn)知誤區(qū),在 CSS 中,優(yōu)先級還需要考慮選擇器的層疊(級聯(lián))順序。

只有在層疊順序相等時,使用哪個值才取決于樣式的優(yōu)先級。

那什么是層疊順序呢?

根據(jù) CSS Cascading 4 最新標(biāo)準(zhǔn):

CSS Cascading and Inheritance Level 5(Current Work)[12]

定義的當(dāng)前規(guī)范下申明的層疊順序優(yōu)先級如下(越往下的優(yōu)先級越高,下面的規(guī)則按升序排列):

  • Normal user agent declarations
  • Normal user declarations
  • Normal author declarations
  • Animation declarations
  • Important author declarations
  • Important user declarations
  • Important user agent declarations
  • Transition declarations

簡單翻譯一下:

按照上述算法,大概是這樣:

過渡動畫過程中每一幀的樣式 > 用戶代理、用戶、頁面作者設(shè)置的!important樣式 > 動畫過程中每一幀的樣式優(yōu)先級 > 頁面作者、用戶、用戶代理普通樣式。

然而,經(jīng)過多個瀏覽器的測試,實際上并不是這樣。(尷尬了)

舉個例子,我們可以通過這個特性,覆蓋掉行內(nèi)樣式中的 !important 樣式:

  1. <p class="txt" style="color:red!important">123456789</p> 
  1. .txt { 
  2.     animation: colorGreen 2s infinite; 
  3. @keyframes colorGreen { 
  4.     0%, 
  5.     100% { 
  6.         color: green; 
  7.     } 

在 Safari 瀏覽器下,上述 DEMO 文本的顏色為綠色,也就是說,處于動畫狀態(tài)中的樣式,能夠覆蓋掉行內(nèi)樣式中的 !important 樣式,屬于最最高優(yōu)先級的一種樣式,我們可以通過無限動畫、或者 animation-fill-mode: forwards,利用這個技巧,覆蓋掉本來應(yīng)該是優(yōu)先級非常非常高的行內(nèi)樣式中的 !important 樣式。

我在早兩年的 Chrome 中也能得到同樣的結(jié)果,但是到今天(2022-01-10),最新版的 Chrome 已經(jīng)不支持動畫過程中關(guān)鍵幀樣式優(yōu)先級覆蓋行內(nèi)樣式 !important 的特性。

對于不同瀏覽器,感興趣的同學(xué)可以利用我這個 DEMO 自行嘗試,CodePen Demo - the priority of CSS Animation[13]

CSS 動畫的優(yōu)化

這也是非常多人非常關(guān)心的一個重點。

我的 CSS 動畫很卡,我應(yīng)該如何去優(yōu)化它?

動畫元素生成獨立的 GraphicsLayer,強制開始 GPU 加速

CSS 動畫很卡,其實是一個現(xiàn)象描述,它的本質(zhì)其實是在動畫過程中,瀏覽器刷新渲染頁面的幀率過低。通常而言,目前大多數(shù)瀏覽器刷新率為 60 次/秒,所以通常來講 FPS 為 60 frame/s 時動畫效果較好,也就是每幀的消耗時間為 16.67ms。

頁面處于動畫變化時,當(dāng)幀率低于一定數(shù)值時,我們就感覺到頁面的卡頓。

而造成幀率低的原因就是瀏覽器在一幀之間處理的事情太多了,超過了 16.67ms,要優(yōu)化每一幀的時間,又需要完整地知道瀏覽器在每一幀干了什么,這個就又涉及到了老生常談的瀏覽器渲染頁面。

到今天,雖然不同瀏覽器的渲染過程不完全相同,但是基本上大同小異,基本上都是:

簡化一下也就是這個圖:

這兩張圖,你可以在非常多不同的文章中看到。

回歸本文的重點,Web 動畫很大一部分開銷在于層的重繪,以層為基礎(chǔ)的復(fù)合模型對渲染性能有著深遠(yuǎn)的影響。當(dāng)不需要繪制時,復(fù)合操作的開銷可以忽略不計,因此在試著調(diào)試渲染性能問題時,首要目標(biāo)就是要避免層的重繪。那么這就給動畫的性能優(yōu)化提供了方向,減少元素的重繪與回流。

這其中,如何減少頁面的回流與重繪呢,這里就會運用到我們常說的** GPU 加速**。

GPU 加速的本質(zhì)其實是減少瀏覽器渲染頁面每一幀過程中的 reflow 和 repaint,其根本,就是讓需要進(jìn)行動畫的元素,生成自己的 GraphicsLayer。

瀏覽器渲染一個頁面時,它使用了許多沒有暴露給開發(fā)者的中間表現(xiàn)形式,其中最重要的結(jié)構(gòu)便是層(layer)。

在 Chrome 中,存在有不同類型的層:RenderLayer(負(fù)責(zé) DOM 子樹),GraphicsLayer(負(fù)責(zé) RenderLayer 的子樹)。

GraphicsLayer ,它對于我們的 Web 動畫而言非常重要,通常,Chrome 會將一個層的內(nèi)容在作為紋理上傳到 GPU 前先繪制(paint)進(jìn)一個位圖中。如果內(nèi)容不會改變,那么就沒有必要重繪(repaint)層。

而當(dāng)元素生成了自己的 GraphicsLayer 之后,在動畫過程中,Chrome 并不會始終重繪整個層,它會嘗試智能地去重繪 DOM 中失效的部分,也就是發(fā)生動畫的部分,在 Composite 之前,頁面是處于一種分層狀態(tài),借助 GPU,瀏覽器僅僅在每一幀對生成了自己獨立 GraphicsLayer 元素層進(jìn)行重繪,如此,大大的降低了整個頁面重排重繪的開銷,提升了頁面渲染的效率。

因此,CSS 動畫(Web 動畫同理)優(yōu)化的第一條準(zhǔn)則就是讓需要動畫的元素生成了自己獨立的 GraphicsLayer,強制開始 GPU 加速,而我們需要知道是,GPU 加速的本質(zhì)是利用讓元素生成了自己獨立的 GraphicsLayer,降低了頁面在渲染過程中重繪重排的開銷。

當(dāng)然,生成自己的獨立的 GraphicsLayer,不僅僅只有 transform3d api,還有非常多的方式。在 CSS 中,包括但不限于(找了很多文檔,沒有很全面的,需要一個一個去嘗試,通過開啟 Chrome 的 Layer border 選項):

  • 3D 或透視變換(perspective、transform) CSS 屬性
  • 使用加速視頻解碼的
  • 擁有 3D (WebGL) 上下文或加速的 2D 上下文的 元素
  • 混合插件(如 Flash)
  • 對自己的 opacity 做 CSS 動畫或使用一個動畫變換的元素
  • 擁有加速 CSS 過濾器的元素
  • 元素有一個包含復(fù)合層的后代節(jié)點(換句話說,就是一個元素?fù)碛幸粋€子元素,該子元素在自己的層里)
  • 元素有一個 z-index 較低且包含一個復(fù)合層的兄弟元素(換句話說就是該元素在復(fù)合層上面渲染)

對于上述一大段非常繞的內(nèi)容,你可以再看看這幾篇文章:

  • 【W(wǎng)eb動畫】CSS3 3D 行星運轉(zhuǎn) && 瀏覽器渲染原理[14]
  • Accelerated Rendering in Chrome[15]

除了上述準(zhǔn)則之外,還有一些提升 CSS 動畫性能的建議:

減少使用耗性能樣式

不同樣式在消耗性能方面是不同的,改變一些屬性的開銷比改變其他屬性要多,因此更可能使動畫卡頓。

例如,與改變元素的文本顏色相比,改變元素的 box-shadow 將需要開銷大很多的繪圖操作。box-shadow 屬性,從渲染角度來講十分耗性能,原因就是與其他樣式相比,它們的繪制代碼執(zhí)行時間過長。這就是說,如果一個耗性能嚴(yán)重的樣式經(jīng)常需要重繪,那么你就會遇到性能問題。

類似的還有 CSS 3D 變換、mix-blend-mode、filter,這些樣式相比其他一些簡單的操作,會更加的消耗性能。我們應(yīng)該盡可能的在動畫過程中降低其使用的頻率或者尋找替代方案。

當(dāng)然,沒有不變的事情,在今天性能很差的樣式,可能明天就被優(yōu)化,并且瀏覽器之間也存在差異。

因此關(guān)鍵在于,我們需要針對每一起卡頓的例子,借助開發(fā)工具來分辨出性能瓶頸所在,然后設(shè)法減少瀏覽器的工作量。學(xué)會 Chrome 開發(fā)者工具的 Performance 面板及其他渲染相關(guān)的面板非常重要,當(dāng)然這不是本文的重點。大家可以自行探索。

使用 will-change 提高頁面滾動、動畫等渲染性能

will-change 為 Web 開發(fā)者提供了一種告知瀏覽器該元素會有哪些變化的方法,這樣瀏覽器可以在元素屬性真正發(fā)生變化之前提前做好對應(yīng)的優(yōu)化準(zhǔn)備工作。這種優(yōu)化可以將一部分復(fù)雜的計算工作提前準(zhǔn)備好,使頁面的反應(yīng)更為快速靈敏。

值得注意的是,用好這個屬性并不是很容易:

  • 不要將 will-change 應(yīng)用到太多元素上:瀏覽器已經(jīng)盡力嘗試去優(yōu)化一切可以優(yōu)化的東西了。有一些更強力的優(yōu)化,如果與 will-change 結(jié)合在一起的話,有可能會消耗很多機器資源,如果過度使用的話,可能導(dǎo)致頁面響應(yīng)緩慢或者消耗非常多的資源。
  • 有節(jié)制地使用:通常,當(dāng)元素恢復(fù)到初始狀態(tài)時,瀏覽器會丟棄掉之前做的優(yōu)化工作。但是如果直接在樣式表中顯式聲明了 will-change 屬性,則表示目標(biāo)元素可能會經(jīng)常變化,瀏覽器會將優(yōu)化工作保存得比之前更久。所以最佳實踐是當(dāng)元素變化之前和之后通過腳本來切換 will-change 的值。
  • 不要過早應(yīng)用 will-change 優(yōu)化:如果你的頁面在性能方面沒什么問題,則不要添加 will-change 屬性來榨取一丁點的速度。will-change 的設(shè)計初衷是作為最后的優(yōu)化手段,用來嘗試解決現(xiàn)有的性能問題。它不應(yīng)該被用來預(yù)防性能問題。過度使用 will-change 會導(dǎo)致大量的內(nèi)存占用,并會導(dǎo)致更復(fù)雜的渲染過程,因為瀏覽器會試圖準(zhǔn)備可能存在的變化過程。這會導(dǎo)致更嚴(yán)重的性能問題。
  • 給它足夠的工作時間:這個屬性是用來讓頁面開發(fā)者告知瀏覽器哪些屬性可能會變化的。然后瀏覽器可以選擇在變化發(fā)生前提前去做一些優(yōu)化工作。所以給瀏覽器一點時間去真正做這些優(yōu)化工作是非常重要的。使用時需要嘗試去找到一些方法提前一定時間獲知元素可能發(fā)生的變化,然后為它加上 will-change 屬性。

有人說 will-change 是良藥,也有人說是毒藥,在具體使用的時候,可以多測試一下。

最后

好了,本文從多個方面,由淺入深地描述了 CSS 動畫我認(rèn)為的一些比較重要、值得一講、需要注意的點。當(dāng)然很多地方點到即止,或者限于篇幅沒有完全展開,很多細(xì)節(jié)還需要讀者進(jìn)一步閱讀規(guī)范或者自行嘗試驗證,實踐出真知,紙上得來終覺淺。

OK,本文到此結(jié)束,希望本文對你有所幫助 :)

想 Get 到最有意思的 CSS 資訊,千萬不要錯過我的公眾號 -- iCSS前端趣聞 😄

更多精彩 CSS 技術(shù)文章匯總在我的 Github -- iCSS[16] ,持續(xù)更新,歡迎點個 star 訂閱收藏。

如果還有什么疑問或者建議,可以多多交流,原創(chuàng)文章,文筆有限,才疏學(xué)淺,文中若有不正之處,萬望告知。

參考資料

[1]CSS Animations Level 1:

https://www.w3.org/TR/2018/WD-css-animations-1-20181011/

[2]純 CSS 實現(xiàn)華為充電動畫:

https://codepen.io/Chokcoco/pen/vYExwvm

[3]純 CSS 實現(xiàn)火焰動畫:

https://codepen.io/Chokcoco/pen/jJJbmz

[4]CodePen Demo -- 利用范圍隨機 animation-duration 和 animation-delay 實現(xiàn)隨機動畫效果:

https://codepen.io/Chokcoco/pen/JjyRYyR

[5]cubic-bezier:

https://cubic-bezier.com/#.25,.1,.25,1

[6]CodePen Demo - 緩動不同效果不同:

https://codepen.io/Chokcoco/pen/JjyxLMY

[7]CodePen Demo -- Sprite Animation with steps() :

https://codepen.io/Chokcoco/pen/JjrBqJZ

[8]CodePen Demo -- Scale Loading steps vs linear:

https://codepen.io/Chokcoco/pen/oNGMROO

[9]CodePen Demo -- CSS Beer!:

https://codepen.io/mikegolus/pen/jJzRwJ

[10]SF - 如何理解 animation-fill-mode:

https://segmentfault.com/q/1010000003867335

[11]深入理解 CSS(Cascading Style Sheets)中的層疊(Cascading):

https://github.com/chokcoco/iCSS/issues/76

[12]CSS Cascading and Inheritance Level 5(Current Work):

https://www.w3.org/TR/css-cascade-5/#cascade-sort

[13]CodePen Demo - the priority of CSS Animation:

https://codepen.io/Chokcoco/pen/PowaXjM

[14]【W(wǎng)eb動畫】CSS3 3D 行星運轉(zhuǎn) && 瀏覽器渲染原理:

https://www.cnblogs.com/coco1s/p/5439619.html

[15]Accelerated Rendering in Chrome:

https://www.html5rocks.com/zh/tutorials/speed/layers/#disqus_thread

[16]Github -- iCSS:

https://github.com/chokcoco/iCSS

 

責(zé)任編輯:姜華 來源: iCSS前端趣聞
相關(guān)推薦

2009-08-17 15:52:42

C#多態(tài)

2009-06-18 10:23:03

Javascript 基本框架

2021-03-16 08:54:35

AQSAbstractQueJava

2011-07-04 10:39:57

Web

2017-07-02 18:04:53

塊加密算法AES算法

2019-01-07 15:29:07

HadoopYarn架構(gòu)調(diào)度器

2021-07-20 15:20:02

FlatBuffers阿里云Java

2012-05-21 10:06:26

FrameworkCocoa

2022-09-26 09:01:15

語言數(shù)據(jù)JavaScript

2019-12-04 10:13:58

Kubernetes存儲Docker

2022-11-09 08:06:15

GreatSQLMGR模式

2021-04-27 08:54:43

ConcurrentH數(shù)據(jù)結(jié)構(gòu)JDK8

2009-11-18 13:30:37

Oracle Sequ

2012-02-21 13:55:45

JavaScript

2018-11-09 16:24:25

物聯(lián)網(wǎng)云計算云系統(tǒng)

2019-11-11 14:51:19

Java數(shù)據(jù)結(jié)構(gòu)Properties

2022-12-02 09:13:28

SeataAT模式

2009-11-30 16:46:29

學(xué)習(xí)Linux

2022-10-31 09:00:24

Promise數(shù)組參數(shù)

2025-03-27 09:38:35

點贊
收藏

51CTO技術(shù)棧公眾號