TailwindCSS 過渡與動畫:transition、animate 全解 | TailwindCSS 完整教學

2026/08/21
TailwindCSS 過渡與動畫:transition、animate 全解 | TailwindCSS 完整教學

上一篇讓元素學會了放大、旋轉、翻面,這一篇讓這些動作真正「流暢起來」。TailwindCSS 的過渡與動畫系統分成兩大支柱:transition(過渡)負責把狀態切換(如 hover 變色、按鈕縮放)變得平順,由屬性(property)、時長(duration)、緩動(ease)、延遲(delay)四要素組成;animate 則提供自主循環的動畫,內建 animate-spinanimate-pinganimate-pulseanimate-bounce 四種常用效果,並可透過 @theme 搭配 @keyframes 定義專屬動畫。而 v4 更帶來 @starting-style 進場動畫、transition-discrete 離散屬性過渡,以及 motion-safemotion-reduce 無障礙變體,讓動效既漂亮又體貼。

前言

上一篇 《變形 Transform》 我們讓元素學會了縮放、旋轉、位移、傾斜,甚至翻進第三維空間。但你應該也注意到,那一整篇的互動效果——hover 放大、箭頭翻轉、卡片翻面——之所以看起來優雅,全都仰賴一個反覆出現的關鍵搭檔:transition。少了它,hover:scale-110 只會「啪」地瞬間跳大,毫無動畫感。這一篇 過渡與動畫(Transitions & Animations),就是要把這位幕後功臣正式請到台前,並且加上它的兄弟——自主循環的 animate-* 動畫。

用一個生活化的類比來理解這兩者的分工:想像你在拍一段影片。 transition(過渡)像是「補間動畫」——你只需要告訴系統「起點是灰色按鈕、終點是藍色按鈕」,中間那幾影格由瀏覽器自動幫你補滿,讓變化滑順地發生;它是被動的,需要某個觸發(滑鼠移入、取得焦點、切換 class)才會啟動,而且一次性地從現值滑到目標值。而 animation(動畫)則像是「預先錄好的循環片段」——你定義好每一段的關鍵影格(@keyframes),它就會自己不停播放,不需要任何觸發、可以無限重複、可以有多個階段。loading 圈持續轉、skeleton 呼吸閃爍、通知點擴散消失,這些「自己會動」的效果都屬於 animation。簡單記:一次性的狀態切換用 transition,會循環、自主動的效果用 animate-*

TailwindCSS v4 是本系列的預設版本,它為動畫系統帶來三項重要更新:一是可直接在 CSS 的 @theme 中定義 @keyframes--animate-* 變數,不再需要 tailwind.config.js二是新增 @starting-style 支援(starting: 變體)與 transition-discrete,讓純 class 就能做出進場動畫;三是 motion-safemotion-reduce 無障礙變體讓你輕鬆尊重使用者的動態偏好。本篇你將學到:

  • transition 四要素transition-*(過渡哪些屬性)、duration-*(時長)、ease-*(緩動曲線)、delay-*(延遲)
  • 內建四動畫animate-spin(旋轉)、animate-ping(擴散)、animate-pulse(呼吸)、animate-bounce(彈跳)的用途與寫法
  • 自訂動畫:用 @theme 定義 @keyframes--animate-*,做出專屬的滑入、搖擺效果
  • v4 進場動畫@starting-stylestarting: 變體)+ transition-discrete,純 class 做 popover/dialog 淡入
  • 無障礙與效能motion-safemotion-reduce 尊重動態偏好,以及 transition-all 的效能陷阱

本系列以 TailwindCSS v4 為預設版本,範例皆可直接執行。凡涉及 v3 差異之處,我會特別標註。

核心概念

過渡與動畫工具的分類全景

Tailwind 的動效 utility 分成 過渡(transition)動畫(animation) 兩大群組。下表先建立全景,涵蓋每個工具的代表 class 與對應 CSS:

群組分類代表 utility對應 CSS作用
過渡過渡屬性transitiontransition-colorstransition-transformtransition-opacitytransition-property指定哪些屬性變化時要平順過渡
過渡全部/無transition-alltransition-nonetransition-property過渡所有屬性/關閉過渡
過渡時長duration-150duration-300duration-700transition-duration過渡花多久完成
過渡緩動ease-linearease-inease-outease-in-outtransition-timing-function過渡的速度曲線
過渡延遲delay-100delay-300delay-1000transition-delay延遲多久才開始過渡
過渡(v4)離散過渡transition-discretetransition-normaltransition-behaviordisplayvisibility 等離散屬性也能過渡
動畫內建動畫animate-spinanimate-pinganimate-pulseanimate-bounceanimation + @keyframes四個開箱即用的循環動畫
動畫關閉animate-noneanimation: none取消動畫
動畫(v4)自訂動畫animate-wiggle(自訂)@theme --animate-* + @keyframes@theme 定義專屬動畫
進場(v4)起始樣式starting:opacity-0starting:open:scale-95@starting-style元素首次插入 DOM 時的起始樣式
無障礙動態偏好motion-safe:*motion-reduce:*@media (prefers-reduced-motion)依使用者偏好開關動畫

不用一次記完。核心心智模型有三句話:transition 是「屬性 + 時長 + 曲線 + 延遲」四要素的組合,讓狀態切換變順;animate-* 是自主循環的動畫,內建四個、其餘可自訂;進場與無障礙是 v4 讓動效更完整、更體貼的加分工具。 抓住這三點,其餘都是刻度與命名細節。

transition 的四要素:屬性、時長、曲線、延遲

一個完整的過渡,是由四個部分拼起來的。理解這四要素,你就能自由調配任何滑順效果:

  • 過渡哪些屬性(transition-property:這是最關鍵的一步——你必須告訴瀏覽器「哪些屬性變化時要平順過渡」transition(預設)會過渡一組常用屬性(顏色、背景、邊框、陰影、opacitytransformfilter 等);transition-colors 只過渡顏色相關;transition-transform 只過渡變形;transition-opacity 只過渡不透明度;transition-all 過渡「所有」屬性(方便但效能較差,下面會談);transition-none 則完全關閉。明確指定要過渡的屬性,是效能與可控性的第一守則。
  • 花多久完成(duration-*:控制過渡時長,數字即毫秒。duration-150(快速 UI 反饋)、duration-200(標準按鈕)、duration-300(標準動畫)、duration-500(慢)、duration-700(很慢)、duration-1000(一秒)。日常互動多落在 150~300ms 之間——太快感覺不到、太慢讓人等得不耐煩。
  • 速度曲線(ease-*:控制過渡的「加速度感」。ease-linear(等速,機械感)、ease-in(慢→快,加速入場,適合離開畫面)、ease-out(快→慢,減速停下,適合進入畫面,最自然)、ease-in-out(慢→快→慢,兩端平緩,預設值)。一個實用心法:元素「進來」用 ease-out、「離開」用 ease-in
  • 延遲多久才開始(delay-*delay-100delay-300delay-1000 等,讓過渡延後啟動。單獨用處不大,但搭配不同元素的不同延遲,就能做出「依序淡入」的交錯(stagger)效果。

這四者組合起來,一個典型的過渡寫法就是:transition-colors duration-200 ease-out(顏色過渡、200 毫秒、減速結束)。四要素若刻度不夠用,全都支援任意值duration-[400ms]delay-[250ms][transition-timing-function:cubic-bezier(0.34,1.56,0.64,1)](自訂彈性曲線)。

內建四動畫:spin、ping、pulse、bounce

Tailwind 開箱即用四個最常用的循環動畫,直接加 class 就會自己動、無限循環,不需任何觸發:

  • animate-spin(旋轉):360 度持續旋轉,loading spinner 的標準做法。通常套在一個圓環 SVG 或圓框元素上。
  • animate-ping(擴散):元素放大並淡出,像雷達波紋擴散,適合通知徽章、線上狀態指示點。實作上通常需要兩層元素——一層擴散、一層固定不動。
  • animate-pulse(呼吸):不透明度在 1 與 0.5 之間來回,做出「呼吸」般的閃爍,是 skeleton 骨架屏載入的招牌效果。單層元素即可。
  • animate-bounce(彈跳):元素上下彈跳,適合「往下捲動」提示箭頭、吸引注意的小圖示

四者中,pulse 是單層元素、ping 常需兩層(一層動一層靜),這是初學最容易搞混的差異。它們的底層都是 Tailwind 內建的 @keyframesspin 對應 rotate(360deg) 的循環、ping 對應「放大到 2 倍並淡出」、pulse 對應 opacity 在 1 與 0.5 之間來回、bounce 對應上下位移搭配不同緩動的彈跳曲線。因為它們都是無限循環(infinite),一旦套上就會持續播放到你移除 class 為止——這也是為什麼下面談無障礙時,會特別強調要替這類持續動畫加上尊重使用者偏好的退場機制。若要關閉,用 animate-none

自訂動畫:@theme + @keyframes(v4)

內建四個不夠用時,v4 讓你直接在 CSS 的 @theme 區塊定義自己的動畫——同時宣告 --animate-* 變數與對應的 @keyframes,就能用 animate-* class 呼叫。這取代了 v3 需要編輯 tailwind.config.js 的做法。例如定義一個搖擺動畫:

@theme {
  --animate-wiggle: wiggle 1s ease-in-out infinite;

  @keyframes wiggle {
    0%, 100% { transform: rotate(-3deg); }
    50%      { transform: rotate(3deg); }
  }
}

定義後,<div class="animate-wiggle"> 就會左右搖擺。--animate-wiggle 的值就是一段標準的 CSS animation 簡寫(名稱、時長、緩動、infinite 循環),而 @keyframes 定義各階段的樣式。此外也可完全不定義變數,直接用任意值呼叫既有 keyframes:animate-[wiggle_1s_ease-in-out_infinite]

v4 進場動畫:@starting-style 與 transition-discrete

這是 v4 全新的能力。傳統上,元素從無到有出現(首次插入 DOM、或從 display:none 顯示)時很難做淡入,因為 display離散屬性、無法過渡。v4 提供兩個工具徹底解決:

  • starting: 變體:對應 CSS 的 @starting-style,設定元素首次出現瞬間的「起始樣式」。例如 starting:opacity-0 表示「元素剛出現時是透明的」,接著它會自動過渡到正常的 opacity-100,形成淡入。
  • transition-discrete:對應 transition-behavior: allow-discrete,讓 displayvisibility 這類離散屬性也能參與過渡,切換時不會瞬間跳變。

兩者搭配,就能純用 class 做出 popover、dialog 的進場淡入,不必再手寫 @starting-style 區塊或靠 JavaScript 控制時序。舉個具體情境:一個 popover 浮層平常是 display:none、被觸發時才顯示,你希望它「淡入 + 微幅放大」出現。傳統寫法因為 displaynone 跳到 block 無法過渡,浮層只會硬生生蹦出來;而 v4 只要在浮層上同時寫 transition-all transition-discrete duration-300 opacity-100 starting:opacity-0 starting:scale-95,就能讓它一出現時從「透明、縮小 95%」平滑過渡到「不透明、原尺寸」,離場時也會反向淡出。這是本篇最能體現「v4 讓純 CSS 動效更完整」的一組工具——過去這類進場動畫幾乎一定要動用 JavaScript 或動畫函式庫,現在幾個 class 就搞定。

實作範例

理論看完,我們用三個高頻範例把 hover 過渡、載入動畫、自訂 keyframes 串起來。以下都是完整的 HTML + Tailwind,可直接貼進頁面觀察效果。

範例一:按鈕 hover 過渡(四要素實戰)

transitiondurationease 是最泛用的互動手法:讓 hover 變色、縮放不再突兀。關鍵是明確指定要過渡的屬性,別無腦用 transition-all

<!-- 標準按鈕:只過渡顏色與陰影,200ms、減速結束 -->
<button class="px-6 py-3 bg-cyan-600 text-white font-semibold rounded-xl
               shadow-sm hover:bg-cyan-700 hover:shadow-md
               transition duration-200 ease-out">
  標準按鈕
</button>

<!-- 邊框按鈕:hover 時填滿背景,只過渡顏色最精準 -->
<button class="px-6 py-3 text-cyan-600 border-2 border-cyan-600 font-semibold rounded-xl
               bg-transparent hover:bg-cyan-600 hover:text-white
               transition-colors duration-200">
  邊框按鈕
</button>

<!-- 互動按鈕:同時放大 + 變色 + 陰影,需要 transition-all -->
<button class="px-6 py-3 bg-indigo-600 text-white font-semibold rounded-xl
               shadow-md hover:bg-indigo-700 hover:scale-105 hover:shadow-lg
               active:scale-95
               transition-all duration-200 ease-out">
  互動按鈕
</button>

三個範例展示過渡四要素的不同組合。標準按鈕transition(預設過渡組,含顏色與陰影)配 duration-200 ease-out,滑鼠移入時顏色與陰影一起滑順變化。邊框按鈕只改顏色,所以用 transition-colors 明確鎖定顏色屬性——這比 transition-all 效能更好、意圖更清楚互動按鈕因為同時要過渡變形(scale)、顏色、陰影三類屬性,這時才用 transition-all 一次涵蓋,並加上 active:scale-95 做出按下凹陷的回饋。緩動上,ease-out(減速停下)讓 hover 效果收得自然。

範例二:載入動畫(spin / pulse / ping 三連發)

內建動畫最常見的舞台就是「載入中」與「通知」。以下把 spinner、skeleton、通知點三種一次示範:

<!-- 1. Loading Spinner:animate-spin 讓圓環持續旋轉 -->
<svg class="animate-spin h-6 w-6 text-cyan-600" fill="none" viewBox="0 0 24 24">
  <circle class="opacity-25" cx="12" cy="12" r="10"
          stroke="currentColor" stroke-width="4"></circle>
  <path class="opacity-75" fill="currentColor"
        d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"></path>
</svg>

<!-- 2. Skeleton 骨架屏:animate-pulse 呼吸閃爍,暗示內容載入中 -->
<div class="bg-white rounded-xl border border-gray-200 p-6 max-w-sm">
  <div class="w-full h-40 bg-gray-200 rounded-lg animate-pulse mb-4"></div>
  <div class="h-4 bg-gray-200 rounded animate-pulse mb-3"></div>
  <div class="h-4 bg-gray-200 rounded animate-pulse w-3/4"></div>
</div>

<!-- 3. 通知點:animate-ping 需要兩層——外層擴散、內層固定 -->
<span class="relative inline-flex h-3 w-3">
  <span class="animate-ping absolute inline-flex h-full w-full
               rounded-full bg-red-400 opacity-75"></span>
  <span class="relative inline-flex rounded-full h-3 w-3 bg-red-500"></span>
</span>

這個範例把三個內建動畫的典型用法一次收齊。Loading Spinneranimate-spin 讓圓環無限旋轉,SVG 內用 opacity-25opacity-75 做出「一段亮、一段淡」的旋轉視覺。Skeleton 骨架屏animate-pulse 讓灰色占位塊呼吸閃爍,是資料未載入時最專業的等待畫面——注意 animate-pulse 是單層元素直接套用通知點則展示 animate-ping 的關鍵結構:必須兩層,外層那個 absolutebg-red-400 負責擴散淡出,內層 relativebg-red-500 固定不動當作實心圓點,兩者疊在一起才有「波紋從點向外擴散」的效果。

範例三:自訂 keyframes 與交錯淡入(v4)

當內建四個不夠用,就在 @theme 定義自己的 @keyframes。以下做一個「滑入淡入」動畫,並用 delay-* 讓列表項目依序登場:

/* 在你的主 CSS 檔(v4 用 @theme 定義,取代 tailwind.config.js) */
@theme {
  --animate-slide-in: slide-in 0.4s ease-out both;

  @keyframes slide-in {
    from { opacity: 0; transform: translateY(12px); }
    to   { opacity: 1; transform: translateY(0); }
  }
}
<!-- 列表項目:套用自訂 animate-slide-in,並用 delay-* 交錯登場 -->
<ul class="space-y-2">
  <li class="bg-white rounded-lg p-4 shadow-sm animate-slide-in delay-0">項目一</li>
  <li class="bg-white rounded-lg p-4 shadow-sm animate-slide-in delay-100">項目二</li>
  <li class="bg-white rounded-lg p-4 shadow-sm animate-slide-in delay-200">項目三</li>
  <li class="bg-white rounded-lg p-4 shadow-sm animate-slide-in delay-300">項目四</li>
</ul>

這個範例展示自訂動畫的完整流程。在 CSS 的 @theme 中,--animate-slide-in 宣告了動畫簡寫(名稱、0.4 秒、ease-outboth 保留起訖狀態),下方的 @keyframes slide-in 定義從「透明並下移 12px」過渡到「不透明並歸位」——這就是 v4 取代 tailwind.config.js 的自訂動畫做法。HTML 端只要加 animate-slide-in class 即可套用,再搭配遞增的 delay-0delay-100delay-200delay-300,四個項目就會依序滑入,做出優雅的交錯(stagger)進場。

v4 提醒:以上 @theme 內定義 @keyframes 的寫法是 v4 才支援的。在 v3,你必須在 tailwind.config.jstheme.extend.keyframestheme.extend.animation 中定義,設定較繁瑣。此外,v4 的 starting: 變體與 transition-discrete 也是 v3 沒有的,v3 要做進場淡入得手寫 @starting-style 或靠 JavaScript。

常見錯誤與最佳實踐

四個最容易踩的動畫坑

坑一:無腦用 transition-all,效能拖累。 transition-all 會監聽「所有」屬性的變化,只要有任何屬性改變(哪怕不是你想要的)都會觸發過渡計算,在複雜元件或長列表上是效能負擔。根治法:明確指定要過渡的屬性——只改顏色用 transition-colors、只改變形用 transition-transform,唯有真的要過渡多類屬性時才用 transition-all

<!-- ❌ 只改顏色卻用 transition-all,多餘且較慢 -->
<button class="hover:bg-cyan-700 transition-all duration-200">按鈕</button>

<!-- ✅ 明確指定,效能好、意圖清楚 -->
<button class="hover:bg-cyan-700 transition-colors duration-200">按鈕</button>

坑二:對 display 做過渡,元素卻瞬間出現/消失。 期望 hidden hover:block transition-opacity 有淡入效果,結果元素「啪」地跳出來——因為 display 是離散屬性,傳統上無法過渡。根治法(v4):用 transition-discrete 搭配 starting: 變體;或退而求其次改用 opacity + pointer-events

<!-- ❌ display(hidden/block)無法過渡,瞬間跳變 -->
<div class="hidden hover:block transition-opacity">內容</div>

<!-- ✅ 改用 opacity + pointer-events,可平滑淡入淡出 -->
<div class="opacity-0 pointer-events-none hover:opacity-100 hover:pointer-events-auto
            transition-opacity duration-300">內容</div>

坑三:持續動畫不尊重使用者的動態偏好,造成不適。 animate-spinanimate-bounceanimate-ping 這類無限循環動畫,對前庭功能障礙、暈動症的使用者可能造成頭暈不適。根治法:用 motion-reduce:animate-nonemotion-safe: 尊重系統的「減少動態效果」設定。

<!-- ✅ 使用者開啟「減少動態」時自動停止跳動 -->
<div class="animate-bounce motion-reduce:animate-none">往下滑 ↓</div>

<!-- ✅ 更保守:只有在使用者不排斥動畫時才啟用 -->
<div class="motion-safe:animate-bounce">往下滑 ↓</div>

坑四:動畫改 widthheighttopleft,畫面卡頓。 用改尺寸或位置屬性來做動畫,會觸發整個版面重排(layout),是最昂貴的操作,長列表或連續動畫明顯卡頓。根治法:動畫優先動 transformopacity(只觸發合成、不重排),這也呼應上一篇《變形 Transform》的效能重點。

過渡與動畫最佳實踐清單

  • 明確指定過渡屬性:優先用 transition-colorstransition-transformtransition-opacity,少用 transition-all
  • 時長抓 150~300ms:互動反饋落在這區間最舒服,duration-1000 這類長過渡別用在需要即時反饋的按鈕上。
  • 進來用 ease-out、離開用 ease-in:符合物理直覺,動效更自然。
  • 狀態切換用 transition、循環自主動用 animate-*:hover 變色用過渡,loading 圈用 animate-spin,分工清楚。
  • 自訂動畫用 @theme:v4 在 @theme 定義 --animate-*@keyframes,不再碰 tailwind.config.js
  • 動畫必配無障礙:持續動畫加 motion-reduce:animate-none 或改用 motion-safe:,尊重使用者偏好。
  • 進場動畫用 v4 新工具:popover/dialog 淡入用 starting: 變體 + transition-discrete,純 class 搞定。
  • 動畫優先動 transformopacity:避免改 widthtopleft,才能跑滿 60 影格不卡頓。

小結

這是 TailwindCSS 完整教學 系列的第二十一篇,也是「視覺樣式(TW-4)」大主題的後段收尾。上一篇 《變形 Transform》 教會元素放大、旋轉、翻面;而這一篇讓這些動作真正流暢起來,並加上自主循環的動畫,替互動效果補上了「時間」這個維度。回顧幾個重點:

  • transition 四要素transition-*(過渡哪些屬性,優先明確指定)、duration-*(時長,互動抓 150~300ms)、ease-*(曲線,進來用 ease-out)、delay-*(延遲,做交錯效果)。
  • 內建四動畫animate-spin(loading 圈)、animate-ping(通知點,需兩層)、animate-pulse(skeleton 呼吸,單層)、animate-bounce(提示彈跳)。
  • 自訂動畫:v4 在 @theme 定義 --animate-*@keyframes,取代 v3 的 tailwind.config.js
  • v4 進場動畫starting: 變體(@starting-style)+ transition-discrete,純 class 做 popover/dialog 淡入。
  • 無障礙與效能motion-reduce:animate-nonemotion-safe: 尊重動態偏好,少用 transition-all,動畫優先動 transformopacity

到這裡,讓畫面「動起來、變好看」的視覺樣式工具你已大致掌握。接下來我們要把視線從單一元素的美化,轉向內容的結構化呈現。下一篇 《表格與清單》 會帶你掌握 table-*border-collapselist-* 等工具,把資料整齊地排進表格與清單裡——從「讓元素好看」進入「讓內容好讀」的新篇章。

BenZ Software Developer

熱愛技術的軟體開發者,在這裡分享程式開發經驗與學習筆記。

本週主打

AI 自動化入門包

你每天手動在做的那些煩事,其實 AI 可以自己跑。這份給你 10 個照著做就會的自動化工作流 + 50 個複製即用的提示詞,不用會寫程式。

看看這個產品 →