iT邦幫忙

2026 iThome 鐵人賽

DAY 19
0
Modern Web

從實務需求出發:前端視覺與 UI 互動開發實踐系列 第 19 篇

【Day 19】動起來:transition、animation 與 @keyframes

  • 分享至 

  • xImage
  •  

昨天把 transform 的「畫法」整理完了:位移、縮放、旋轉、傾斜,還有決定從哪個點動的 transform-origin。不過那些都是靜態的,元素只是被擺在另一個位置或角度。接下來要來介紹如何讓畫面動起來

先從簡單的狀態漸變 transition,再到能逐幀設定的 animation。那我們開始吧!


一、transition:狀態之間的漸變

預設下,元素的 hover、active 等互動樣式改變是「瞬間」發生,有明顯不連貫。我們能用 transition 屬性,將這個瞬間變成一段漸變動畫,讓元素從 A 狀態慢慢變到 B 狀態。

.cup {
  translate: 0 0;
  transition: translate 0.3s ease-out;
}

.cup:hover {
  translate: 0 -8px; /* hover 時,0.3 秒內慢慢往上移 */
}

transition 是下表四個屬性的簡寫,負責告訴瀏覽器:哪個屬性要漸變、花多久時間、變化速率、延遲時間。至於不同狀態的樣式,則是像 .cup 和 .cup:hover 這樣,寫在對應狀態的選取器中。

屬性 作用 預設值
transition-property 哪些屬性要漸變 all
transition-duration 漸變多久(第一個時間參數) 0s
transition-timing-function 動畫速率曲線 ease
transition-delay 延遲多久才執行(第二個時間參數) 0s

💡 動畫速率曲線,除了預設關鍵字(ease、linear、ease-in、ease-out、ease-in-out),還能用曲線函式 cubic-bezier() 自訂速度變化。

可以接受多個漸變屬性,用逗號分隔:

.cup {
  transition: translate 0.3s ease-out, opacity 0.2s linear;
}

需要注意的點

  • 需要有人觸發:transition 本身不會自己開始,必須有狀態改變才會觸發漸變動畫,例如 :hover、:focus,或是用 JS 換 class。
  • 只有「起點」和「終點」:無法設定中間段、循環播放。
  • 要寫在起始狀態:transition 放在 .cup 而不是 .cup:hover,滑鼠移開時也會有漸變;只放在 :hover 的話,離開時會瞬間跳回去。
  • 部分屬性無效:沒有中間值可以算的屬性(例如 display、border-style、position)無法被漸變。

transition 很適合 hover、focus 這類「簡單的狀態切換」。只要需求出現「自己動」、「重複」或「多階變化」,就該換 animation 了。


二、關鍵影格動畫

想做出更加靈動的動畫效果,需要依賴「關鍵影格動畫」。以 2D 動畫為例,它是由一張張有些微差距的圖像連續切換播放而成。這一連串的圖像中有所謂的「關鍵幀 (keyframe)」,用來說明特定時間點的畫面狀態。其他動畫師就能依關鍵幀的變化,補上中間過渡的圖像。

在 CSS 屬性中,animation 與 @keyframes 就是類似的運作方式。先用 @keyframes 定義動畫的關鍵幀,再用 animation 指定動畫與其播放設定。

@keyframes:定義動畫

@keyframes bounce {
  0%   { translate: 0 0; }
  50%  { translate: 0 -20px; }
  100% { translate: 0 0; }
}

百分比代表「播一次的進度」,0% 是開頭、100% 是結尾,瀏覽器會自動補出中間的每一幀。只有頭尾兩格時,可以用 from 和 to 取代(個人習慣都寫百分比,比較直觀)。

  • 多個百分比可以共用同一幀,例如開頭與結束畫面都一樣,就可以合在一起寫 0%, 100% { ... }。
  • 某一格如果沒寫某個屬性,瀏覽器會用「前後有寫那個屬性的兩格」去推算中間值;如果頭尾格整個沒寫,則是以元素原本的樣式當起點或終點。

💡 跟 transition 一樣,沒有中間值可以算的屬性是無法被補幀的,例如 display。

animation:動畫播放設定

.ball {
  animation: bounce 1.2s ease-in-out infinite;
}

這行的意思是:套用名為 bounce 的 keyframes 動畫,動畫長度 1.2 秒,變化速率是 ease-in-out,重複播放。

animation 是下面這些屬性的簡寫,需要單獨調整時可以拆開寫:

屬性 作用 預設值
animation-name 動畫名稱,寫 @keyframes 定義時取的名稱 none
animation-duration 播放時間(第一個時間參數) 0s
animation-timing-function 速度曲線 ease
animation-delay 延遲多久才開始(第二個時間參數) 0s
animation-iteration-count 播放次數 1
animation-direction 播放方向:normal、reverse、alternate、alternate-reverse normal
animation-fill-mode 填充模式,決定播放前後要不要保留動畫的樣式:none、forwards、backwards、both none
animation-play-state 播放狀態,running 或 paused running

實用的設定:

  1. forwards:動畫結束後,停在最後一格的樣子,而不是跳回原本的樣式。做「進場」這類只播一次的動畫幾乎一定會用到。
    進場動畫示範

  2. alternate:奇數輪正著播、偶數輪倒著播,來回晃動的動畫用它,就不用在 @keyframes 裡自己寫回程。
    來回播放示範

💡animation-delay 可以是負數,會讓動畫從已經播了一段時間的位置開始。例如一組元素動畫想同,但想讓動作錯開,可以給每個元素不同的負數延遲。
動作錯開示範


三、timing-function 補充

animation-timing-function 是決定「進度怎麼隨時間前進」,我自己有寫過但還是覺得蠻抽象的。我們先從幾個常見關鍵字,來看看實際呈現的效果:

  • linear:等速,轉圈、跑馬燈這類持續性的動畫常用。
  • ease:預設值,先慢、中間快、再慢一點。
  • ease-in:越來越快,適合「離場動畫」。
  • ease-out:越來越慢,適合「進場動畫」,感覺像是自然減速停下來。
  • ease-in-out:兩頭慢、中間快,適合來回晃動。

timing-function 示範

還記得前面提到曲線函式 cubic-bezier() 嗎?這些關鍵字其實就是一條條預設好的 cubic-bezier(x1, y1, x2, y2) 曲線。同理,需要更細的動畫表現時可以自己寫。

另外,寫在 animation 上的 timing-function,是在每兩格 keyframe 之間作用,而不是整個動畫。如果想讓某一段特別快或特別慢,可以直接寫在那一格裡:

@keyframes bounce {
  0%   { translate: 0 0;     animation-timing-function: ease-out; }
  50%  { translate: 0 -20px; animation-timing-function: ease-in; }
  100% { translate: 0 0; }
}

cubic-bezier() 示範
這樣上升和下降會各自有不同的節奏,比整段用同一個曲線更像是真的在彈跳。


四、CSS 變數提升動畫的複用性

同一份 @keyframes 想套在很多元素上,但每個元素的角度、延遲、距離都些許不同。且慢!先不要急著 Ctrl+C、Ctrl+V,這時候可以在 @keyframes 裡與 animation 屬性中用 var(--變數名, 預設值),當在為不同元素設定動畫時,就能直接對對應 CSS 變數賦予新值。

.deco {
  animation: float 7s ease-in-out infinite;
  animation-delay: calc(var(--delay) + var(--offset, 0s)); // offset 預設 0s
}

.deco-a { --delay: 2s; --offset: -1.6s; --rot: 18deg; }
.deco-b { --delay: 2.4s; --offset: -3.1s; --rot: -15deg; }

@keyframes float {
  0%, 100% { transform: rotate(var(--rot)); }
  50%      { transform: rotate(calc(var(--rot) + 0.65deg)); }
}

當一組元素的動畫各自有點不同時,只改需要變數、不用改動畫本身,在微調時也能比較直觀在修改哪些設定。

💡 calc() 可以直接用在 animation-delay 這類時間值上,把基礎延遲和個別微調組合起來。


參考資料


上一篇
【Day 18】動起來:重新認識 transform
系列文
從實務需求出發:前端視覺與 UI 互動開發實踐 共 19 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言