Vue3 教學:樣式與 CSS 管理 – 動態 class / style 綁定
簡介
在單頁應用程式 (SPA) 中,樣式的切換與變化往往是使用者互動的關鍵。Vue3 透過 v-bind:class 與 v-bind:style(簡寫為 :class、:style)提供了直觀且高效的動態 CSS 綁定機制,讓開發者可以根據資料狀態即時改變元素的外觀,而不必在程式碼中手動操作 DOM。
本單元將深入探討 Vue3 的動態 class 與 style 綁定,說明其語法、背後原理,並提供多個實務範例,協助你在開發過程中 快速、乾淨地管理樣式,提升程式碼可讀性與維護性。
核心概念
1. 為什麼使用動態綁定?
- 資料驅動:Vue 的響應式系統會在資料變化時自動更新樣式,避免手動 DOM 操作。
- 條件式樣式:可以根據布林值、計算屬性或陣列/物件的內容,決定套用哪一個 class。
- 可維護性:樣式邏輯集中於 Vue 組件的
data/computed,樣式名稱與 CSS 本身保持分離。
2. :class 的三種寫法
| 寫法 | 說明 | 範例 |
|---|---|---|
| 字串 | 直接寫 class 名稱,適合單一固定 class。 | :class="'active'" |
| 陣列 | 多個 class,支援條件式 (三元運算子) 或計算屬性。 | :class="[isRed ? 'red' : '', sizeClass]" |
| 物件 | key 為 class 名稱,value 為布林值,值為 true 時套用。 |
:class="{ active: isActive, disabled: isDisabled }" |
小技巧:在
script setup中使用computed產生物件或陣列,可讓模板保持簡潔。
3. :style 的兩種寫法
| 寫法 | 說明 | 範例 |
|---|---|---|
| 字串 | 直接寫 CSS 文字,適合單一屬性。 | :style="'color: red;'" |
| 物件 | key 為 CSS 屬性(駝峰或 kebab-case),value 為對應值。 | :style="{ backgroundColor: bgColor, fontSize: fontSize + 'px' }" |
注意:若屬性名稱使用 kebab-case,必須加上引號(例如
'"font-weight"'),或直接使用駝峰式fontWeight。
4. 計算屬性與方法的配合
使用 computed 可以把「樣式邏輯」抽離出來,使模板更具可讀性:
<script setup>
import { ref, computed } from 'vue'
const isActive = ref(false)
const size = ref('md') // 'sm' | 'md' | 'lg'
const buttonClass = computed(() => ({
btn: true,
'btn--active': isActive.value,
[`btn--${size.value}`]: true // 動態產生 btn--sm / btn--md / btn--lg
}))
</script>
<template>
<button :class="buttonClass">按鈕</button>
</template>
程式碼範例
下面提供 5 個實用範例,涵蓋字串、陣列、物件、計算屬性與混合使用的情境。每個範例皆附上說明與註解。
範例 1:簡單字串綁定(單一 class)
<template>
<!-- 當 isError 為 true 時,加上 error 類別 -->
<p :class="isError ? 'error' : ''">{{ message }}</p>
</template>
<script setup>
import { ref } from 'vue'
const isError = ref(true)
const message = ref('資料載入失敗')
</script>
<style>
.error {
color: #e74c3c;
font-weight: bold;
}
</style>
重點:使用三元運算子直接在模板中決定 class,適合條件簡單的情況。
範例 2:陣列綁定 + 多條件
<template>
<div :class="badgeClasses">Badge</div>
<button @click="toggle()">切換狀態</button>
</template>
<script setup>
import { ref, computed } from 'vue'
const isSuccess = ref(false)
const isWarning = ref(true)
const badgeClasses = computed(() => [
'badge', // 永遠套用
isSuccess.value && 'badge--success', // 成功時
isWarning.value && 'badge--warning' // 警告時
].filter(Boolean)) // 移除 false
</script>
<style>
.badge { padding: 4px 8px; border-radius: 4px; }
.badge--success { background: #2ecc71; color: #fff; }
.badge--warning { background: #f1c40f; color: #fff; }
</style>
技巧:
filter(Boolean)可以自動把false、null、undefined移除,避免產生空字串。
範例 3:物件綁定(最常用)
<template>
<button :class="btnClass" @click="isActive = !isActive">
{{ isActive ? '已啟用' : '未啟用' }}
</button>
</template>
<script setup>
import { ref, computed } from 'vue'
const isActive = ref(false)
const btnClass = computed(() => ({
btn: true,
'btn--active': isActive.value,
'btn--disabled': !isActive.value
}))
</script>
<style>
.btn { padding: 8px 16px; border: none; cursor: pointer; }
.btn--active { background: #3498db; color: #fff; }
.btn--disabled { background: #bdc3c7; color: #2c3e50; }
</style>
說明:物件的 key 為 class 名稱,value 為布林值,Vue 只會在值為
true時套用對應的 class。
範例 4:動態 style 物件 + 單位處理
<template>
<div :style="boxStyle" class="box"></div>
<input type="range" min="50" max="300" v-model="size" /> 大小
</template>
<script setup>
import { ref, computed } from 'vue'
const size = ref(150) // 直接綁定在 input 上
const bgColor = ref('#e67e22')
const boxStyle = computed(() => ({
width: size.value + 'px',
height: size.value + 'px',
backgroundColor: bgColor.value,
borderRadius: `${size.value / 10}px`
}))
</script>
<style>
.box {
transition: all 0.3s ease;
margin-top: 16px;
}
</style>
重點:在 style 物件中自行拼接單位(如
px、%),或使用模板字串讓程式碼更易讀。
範例 5:結合 :class 與 :style 的複合範例(卡片切換)
<template>
<section class="card" :class="cardState" :style="cardStyle">
<h3>{{ title }}</h3>
<p>{{ content }}</p>
<button @click="toggle()">切換模式</button>
</section>
</template>
<script setup>
import { ref, computed } from 'vue'
const title = ref('動態卡片')
const content = ref('這是一張可以切換樣式的卡片。')
const isDark = ref(false)
const cardState = computed(() => ({
'card--dark': isDark.value,
'card--light': !isDark.value
}))
const cardStyle = computed(() => ({
// 文字顏色根據主題自動改變
color: isDark.value ? '#ecf0f1' : '#2c3e50',
// 背景使用漸層
background: isDark.value
? 'linear-gradient(135deg, #34495e, #2c3e50)'
: 'linear-gradient(135deg, #fff, #f8f9fa)'
}))
function toggle() {
isDark.value = !isDark.value
}
</script>
<style>
.card {
padding: 24px;
border-radius: 8px;
transition: all 0.4s ease;
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}
.card--dark { border: 1px solid #2c3e50; }
.card--light { border: 1px solid #dcdcdc; }
</style>
實務應用:在主題切換、狀態指示、交互式動畫等情境中,同時使用 class 與 style 可讓樣式更具彈性與可讀性。
常見陷阱與最佳實踐
| 陷阱 | 說明 | 解決方案 |
|---|---|---|
| 直接在模板寫長字串 | 大量條件會讓模板變得雜亂。 | 把 class 物件或陣列抽成 computed,保持模板簡潔。 |
| 忘記加單位 | width: 100 會被視為 100px? 不是,瀏覽器會忽略。 |
在 style 物件中自行拼接單位,或使用 Number + 'px'。 |
| 使用 kebab-case 時忘記加引號 | :style="{ 'font-size': size + 'px' }" 必須加引號。 |
建議統一使用駝峰式 (fontSize) 以避免錯誤。 |
過度依賴 v-if 取代 class 切換 |
每次切換都會重新渲染 DOM,效能下降。 | 盡量使用 class / style 綁定,僅在結構真的需要變化時才使用 v-if。 |
| 樣式衝突 | 多個 class 同時套用時,優先順序不易掌握。 | 使用 BEM 命名法或 CSS Modules,確保權重可預測。 |
最佳實踐
- 保持樣式與邏輯分離:CSS 放在
<style>(或獨立檔案),Vue 只負責決定 哪 個 class 被套用。 - 使用
computed:把判斷式集中在computed,讓模板只呈現「資料」而非「邏輯」。 - 命名一致:採用 BEM 或 CSS Modules,避免全域樣式污染。
- 避免內嵌過多 style:除非是動態屬性(如寬度、顏色),否則盡量使用 class。
- 利用
transition:配合:class的切換,可直接使用 CSStransition或animation,實現流暢過渡。
實際應用場景
| 場景 | 為何適合使用動態 class / style |
|---|---|
| 表單驗證提示 | 根據驗證結果切換 error / success class,顯示不同顏色與圖示。 |
| 暗黑模式切換 | 只改變根元素的 theme--dark / theme--light class,子元件自動繼承樣式。 |
| 即時圖表或儀表板 | 根據資料值切換顏色(如 green、red),或調整寬度/高度以呈現比例。 |
| 多語系 UI | 依語系切換字體或排版方向(rtl / ltr)的 class。 |
| 動畫與過渡 | 使用 enter-active、leave-active 等 class 搭配 transition,在條件變化時自動觸發動畫。 |
總結
動態 class 與 style 綁定是 Vue3 中最實用的樣式管理方式。透過 :class(字串、陣列、物件)與 :style(字串、物件)配合 計算屬性,我們可以:
- 根據資料狀態即時切換樣式,不必手動操作 DOM。
- 保持模板簡潔,將判斷邏輯集中於
computed。 - 提升可維護性,讓 CSS 與邏輯分離,方便團隊協作。
- 減少效能損耗,避免不必要的重新渲染或樣式衝突。
掌握上述概念與實務技巧後,你將能在 各種 UI 互動、主題切換、即時資料視覺化 場景中,快速且優雅地控制樣式,寫出 乾淨、可讀、易維護 的 Vue3 應用程式。祝你開發順利,玩得開心!