昨天把 transform 的「畫法」整理完了:位移、縮放、旋轉、傾斜,還有決定從哪個點動的 transform-origin。不過那些都是靜態的,元素只是被擺在另一個位置或角度。接下來要來介紹如何讓畫面動起來
先從簡單的狀態漸變 transition,再到能逐幀設定的 animation。那我們開始吧!
預設下,元素的 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 bounce {
0% { translate: 0 0; }
50% { translate: 0 -20px; }
100% { translate: 0 0; }
}
百分比代表「播一次的進度」,0% 是開頭、100% 是結尾,瀏覽器會自動補出中間的每一幀。只有頭尾兩格時,可以用 from 和 to 取代(個人習慣都寫百分比,比較直觀)。
0%, 100% { ... }。💡 跟
transition一樣,沒有中間值可以算的屬性是無法被補幀的,例如display。
.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 |
實用的設定:
forwards:動畫結束後,停在最後一格的樣子,而不是跳回原本的樣式。做「進場」這類只播一次的動畫幾乎一定會用到。
alternate:奇數輪正著播、偶數輪倒著播,來回晃動的動畫用它,就不用在 @keyframes 裡自己寫回程。
💡
animation-delay可以是負數,會讓動畫從已經播了一段時間的位置開始。例如一組元素動畫想同,但想讓動作錯開,可以給每個元素不同的負數延遲。
animation-timing-function 是決定「進度怎麼隨時間前進」,我自己有寫過但還是覺得蠻抽象的。我們先從幾個常見關鍵字,來看看實際呈現的效果:
linear:等速,轉圈、跑馬燈這類持續性的動畫常用。ease:預設值,先慢、中間快、再慢一點。ease-in:越來越快,適合「離場動畫」。ease-out:越來越慢,適合「進場動畫」,感覺像是自然減速停下來。ease-in-out:兩頭慢、中間快,適合來回晃動。
還記得前面提到曲線函式 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; }
}

這樣上升和下降會各自有不同的節奏,比整段用同一個曲線更像是真的在彈跳。
同一份 @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這類時間值上,把基礎延遲和個別微調組合起來。