[Medium] 🎨 多主題切換實作
📋 面試情境題
Q: 當一個頁面要做 2 種不同風格(例如亮色/暗色主題),怎麼安排 CSS?
這是一個考察 CSS 架構設計和實務經驗的問題,涉及:
- CSS 架構設計
- 主題切換策略
- 現代化工具應用(Tailwind CSS、CSS Variables)
- 效能與維護性考量
解決方案總覽
| 方案 | 適用場景 | 優點 | 缺點 | 推薦度 |
|---|---|---|---|---|
| CSS Variables | 現代瀏覽器專案 | 動態切換、效能好 | IE 不支援 | 5/5 強烈推薦 |
| Quasar + Pinia + SCSS | Vue 3 + Quasar 專案 | 完整生態、狀態管理、易維護 | 需 Quasar Framework | 5/5 強烈推薦 |
| Tailwind CSS | 快速開發、設計系統 | 開發快速、一致性高 | 學習曲線、HTML 冗長 | 5/5 強烈推薦 |
| CSS Class 切換 | 需相容舊瀏覽器 | 相容性好 | CSS 體積較大 | 4/5 推薦 |
| CSS Modules | React/Vue 元件化專案 | 作用域隔離 | 需打包工具 | 4/5 推薦 |
| Styled Components | React 專案 | CSS-in-JS、動態樣式 | Runtime 開銷 | 4/5 推薦 |
| SASS/LESS 變數 | 需編譯時決定主題 | 功能強大 | 無法動態切換 | 3/5 可考慮 |
| 獨立 CSS 檔案 | 主題差異大、完全獨立 | 清晰分離 | 載入開銷、重複程式碼 | 2/5 不推薦 |
方案 1:CSS Variables
核心概念
使用 CSS 自訂屬性(CSS Custom Properties),透過切換根元素的 class 來改變變數值。
實作 方式
1. 定義主題變數
/* styles/themes.css */
/* 亮色主題(預設) */
:root {
--color-primary: #3b82f6;
--color-secondary: #8b5cf6;
--color-background: #ffffff;
--color-text: #1f2937;
--color-border: #e5e7eb;
--color-card: #f9fafb;
--shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
/* 暗色主題 */
[data-theme='dark'] {
--color-primary: #60a5fa;
--color-secondary: #a78bfa;
--color-background: #1f2937;
--color-text: #f9fafb;
--color-border: #374151;
--color-card: #111827;
--shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
}
/* 如果有第三種主題(例如護眼模式) */
[data-theme='sepia'] {
--color-primary: #92400e;
--color-secondary: #78350f;
--color-background: #fef3c7;
--color-text: #451a03;
--color-border: #fde68a;
--color-card: #fef9e7;
--shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
}
2. 使用變數
/* components/Button.css */
.button {
background-color: var(--color-primary);
color: var(--color-text);
border: 1px solid var(--color-border);
box-shadow: var(--shadow);
transition: all 0.3s ease;
}
.card {
background-color: var(--color-card);
color: var(--color-text);
border: 1px solid var(--color-border);
}
body {
background-color: var(--color-background);
color: var(--color-text);
}
3. JavaScript 切換主題
// utils/theme.js
// 取得當前主題
function getCurrentTheme() {
return localStorage.getItem('theme') || 'light';
}
// 設定主題
function setTheme(theme) {
document.documentElement.setAttribute('data-theme', theme);
localStorage.setItem('theme', theme);
}
// 切換主題
function toggleTheme() {
const currentTheme = getCurrentTheme();
const newTheme = currentTheme === 'light' ? 'dark' : 'light';
setTheme(newTheme);
}
// 初始化(從 localStorage 讀取使用者偏好)
function initTheme() {
const savedTheme = getCurrentTheme();
setTheme(savedTheme);
// 監聽系統主題變化
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
mediaQuery.addEventListener('change', (e) => {
if (!localStorage.getItem('theme')) {
// 如果使用者沒有設定偏好,跟隨系統
setTheme(e.matches ? 'dark' : 'light');
}
});
}
// 頁面載入時初始化
initTheme();
4. Vue 3 整合範例
<template>
<div>
<button @click="toggleTheme" class="theme-toggle">
<span v-if="currentTheme === 'light'">🌙 暗色模式</span>
<span v-else>☀️ 亮色模式</span>
</button>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
const currentTheme = ref('light');
function toggleTheme() {
const newTheme = currentTheme.value === 'light' ? 'dark' : 'light';
currentTheme.value = newTheme;
document.documentElement.setAttribute('data-theme', newTheme);
localStorage.setItem('theme', newTheme);
}
onMounted(() => {
const savedTheme = localStorage.getItem('theme') || 'light';
currentTheme.value = savedTheme;
document.documentElement.setAttribute('data-theme', savedTheme);
});
</script>
優點
- ✅ 動態切換:無需重新載入 CSS 檔案
- ✅ 效能好:瀏覽器原生支援,只改變變數值
- ✅ 易維護:主題集中管理,修改方便
- ✅ 可擴展:輕鬆添加第三、第四種主題
缺點
- ❌ IE 不支援:需要 polyfill 或降級方案
- ❌ 預處理器整合:與 SASS/LESS 變數混用需注意
方案 2:Tailwind CSS
核心概念
使用 Tailwind CSS 的 dark: 變體和自訂主題配置,搭配 class 切換實現主題。