主题管理 #

什么是主题管理? #

主题管理是指对应用程序的外观样式进行统一管理和切换的能力,最常见的场景是深色模式和浅色模式切换。

text
┌─────────────────────────────────────────────────────────────┐
│                     主题管理                                 │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│   ┌─────────────────────────┐  ┌─────────────────────────┐  │
│   │      浅色主题           │  │      深色主题           │  │
│   │  ┌─────────────────┐   │  │  ┌─────────────────┐   │  │
│   │  │  背景: 白色      │   │  │  │  背景: 深色      │   │  │
│   │  │  文字: 黑色      │   │  │  │  文字: 白色      │   │  │
│   │  │  适合白天使用    │   │  │  │  适合夜间使用    │   │  │
│   │  └─────────────────┘   │  │  └─────────────────┘   │  │
│   └─────────────────────────┘  └─────────────────────────┘  │
│                                                              │
└─────────────────────────────────────────────────────────────┘

基础函数 #

主题管理模块 #

javascript
const THEME_KEY = 'dools-theme';

export const getTheme = () => {
    if (typeof window === 'undefined') return 'light';
    
    const savedTheme = localStorage.getItem(THEME_KEY);
    if (savedTheme && (savedTheme === 'light' || savedTheme === 'dark')) {
        return savedTheme;
    }
    
    if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
        return 'dark';
    }
    
    return 'light';
};

export const setTheme = (theme) => {
    if (typeof window === 'undefined') return;
    
    document.documentElement.setAttribute('data-theme', theme);
    localStorage.setItem(THEME_KEY, theme);
};

export const toggleTheme = () => {
    const currentTheme = getTheme();
    const newTheme = currentTheme === 'light' ? 'dark' : 'light';
    setTheme(newTheme);
    return newTheme;
};

export const initTheme = () => {
    const theme = getTheme();
    setTheme(theme);
    return theme;
};

export const watchSystemTheme = (callback) => {
    if (typeof window === 'undefined') return;
    
    const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
    
    const handleChange = (e) => {
        const savedTheme = localStorage.getItem(THEME_KEY);
        if (!savedTheme) {
            const newTheme = e.matches ? 'dark' : 'light';
            setTheme(newTheme);
            callback && callback(newTheme);
        }
    };
    
    mediaQuery.addEventListener('change', handleChange);
    
    return () => {
        mediaQuery.removeEventListener('change', handleChange);
    };
};

基本使用 #

javascript
import { getTheme, setTheme, toggleTheme, initTheme } from './utils/theme';

initTheme();

const currentTheme = getTheme();
console.log(currentTheme);

setTheme('dark');

const newTheme = toggleTheme();
console.log(newTheme);

CSS 主题实现 #

使用 CSS 变量 #

css
:root {
    --bg-primary: #ffffff;
    --bg-secondary: #f5f5f5;
    --text-primary: #333333;
    --text-secondary: #666666;
    --border-color: #e0e0e0;
    --shadow-color: rgba(0, 0, 0, 0.1);
}

[data-theme="dark"] {
    --bg-primary: #1a1a1a;
    --bg-secondary: #2d2d2d;
    --text-primary: #ffffff;
    --text-secondary: #b0b0b0;
    --border-color: #404040;
    --shadow-color: rgba(0, 0, 0, 0.3);
}

body {
    background-color: var(--bg-primary);
    color: var(--text-primary);
    transition: background-color 0.3s, color 0.3s;
}

.card {
    background-color: var(--bg-secondary);
    border: 1px solid var(--border-color);
    box-shadow: 0 2px 8px var(--shadow-color);
}

使用 CSS 类名 #

css
.theme-light {
    --bg-primary: #ffffff;
    --text-primary: #333333;
}

.theme-dark {
    --bg-primary: #1a1a1a;
    --text-primary: #ffffff;
}
javascript
const setThemeByClass = (theme) => {
    document.body.className = `theme-${theme}`;
    localStorage.setItem(THEME_KEY, theme);
};

进阶用法 #

主题管理类 #

javascript
class ThemeManager {
    constructor(options = {}) {
        this.key = options.storageKey || 'app-theme';
        this.defaultTheme = options.defaultTheme || 'light';
        this.themes = options.themes || ['light', 'dark'];
        this.listeners = [];
        
        this.init();
    }
    
    init() {
        const theme = this.get();
        this.apply(theme);
        this.watchSystem();
    }
    
    get() {
        const saved = localStorage.getItem(this.key);
        if (saved && this.themes.includes(saved)) {
            return saved;
        }
        return this.getSystemPreference() || this.defaultTheme;
    }
    
    set(theme) {
        if (!this.themes.includes(theme)) return;
        this.apply(theme);
        localStorage.setItem(this.key, theme);
        this.notify(theme);
    }
    
    apply(theme) {
        document.documentElement.setAttribute('data-theme', theme);
    }
    
    toggle() {
        const current = this.get();
        const currentIndex = this.themes.indexOf(current);
        const nextIndex = (currentIndex + 1) % this.themes.length;
        const nextTheme = this.themes[nextIndex];
        this.set(nextTheme);
        return nextTheme;
    }
    
    getSystemPreference() {
        if (!window.matchMedia) return null;
        const isDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
        return isDark ? 'dark' : 'light';
    }
    
    watchSystem() {
        if (!window.matchMedia) return;
        
        const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
        mediaQuery.addEventListener('change', (e) => {
            if (!localStorage.getItem(this.key)) {
                this.set(e.matches ? 'dark' : 'light');
            }
        });
    }
    
    onChange(callback) {
        this.listeners.push(callback);
        return () => {
            this.listeners = this.listeners.filter(cb => cb !== callback);
        };
    }
    
    notify(theme) {
        this.listeners.forEach(callback => callback(theme));
    }
}

const themeManager = new ThemeManager({
    storageKey: 'my-app-theme',
    defaultTheme: 'light',
    themes: ['light', 'dark']
});

themeManager.onChange((theme) => {
    console.log('主题切换为:', theme);
});

多主题支持 #

javascript
class MultiThemeManager {
    constructor() {
        this.themes = {
            light: {
                name: '浅色',
                colors: {
                    primary: '#3b82f6',
                    background: '#ffffff',
                    text: '#333333'
                }
            },
            dark: {
                name: '深色',
                colors: {
                    primary: '#60a5fa',
                    background: '#1a1a1a',
                    text: '#ffffff'
                }
            },
            ocean: {
                name: '海洋',
                colors: {
                    primary: '#0891b2',
                    background: '#ecfeff',
                    text: '#164e63'
                }
            },
            forest: {
                name: '森林',
                colors: {
                    primary: '#059669',
                    background: '#ecfdf5',
                    text: '#064e3b'
                }
            }
        };
    }
    
    applyTheme(themeName) {
        const theme = this.themes[themeName];
        if (!theme) return;
        
        Object.entries(theme.colors).forEach(([key, value]) => {
            document.documentElement.style.setProperty(`--color-${key}`, value);
        });
        
        localStorage.setItem('theme', themeName);
    }
    
    getThemeList() {
        return Object.entries(this.themes).map(([key, value]) => ({
            id: key,
            name: value.name
        }));
    }
}

主题过渡动画 #

javascript
const setThemeWithTransition = (theme) => {
    document.documentElement.classList.add('theme-transition');
    
    document.documentElement.setAttribute('data-theme', theme);
    localStorage.setItem(THEME_KEY, theme);
    
    setTimeout(() => {
        document.documentElement.classList.remove('theme-transition');
    }, 300);
};
css
.theme-transition,
.theme-transition *,
.theme-transition *::before,
.theme-transition *::after {
    transition: background-color 0.3s, color 0.3s, border-color 0.3s !important;
}

实际应用场景 #

1. React 主题 Hook #

javascript
import { useState, useEffect, useCallback } from 'react';
import { getTheme, setTheme, toggleTheme, watchSystemTheme } from './utils/theme';

export const useTheme = () => {
    const [theme, setThemeState] = useState(getTheme);
    
    useEffect(() => {
        const cleanup = watchSystemTheme((newTheme) => {
            setThemeState(newTheme);
        });
        return cleanup;
    }, []);
    
    const handleSetTheme = useCallback((newTheme) => {
        setTheme(newTheme);
        setThemeState(newTheme);
    }, []);
    
    const handleToggleTheme = useCallback(() => {
        const newTheme = toggleTheme();
        setThemeState(newTheme);
        return newTheme;
    }, []);
    
    return {
        theme,
        setTheme: handleSetTheme,
        toggleTheme: handleToggleTheme,
        isDark: theme === 'dark'
    };
};

const ThemeToggle = () => {
    const { theme, toggleTheme, isDark } = useTheme();
    
    return (
        <button onClick={toggleTheme}>
            {isDark ? '🌙 深色' : '☀️ 浅色'}
        </button>
    );
};

2. Vue 主题 Composable #

javascript
import { ref, watch, onMounted, onUnmounted } from 'vue';
import { getTheme, setTheme, toggleTheme, watchSystemTheme } from './utils/theme';

export const useTheme = () => {
    const theme = ref(getTheme());
    
    const handleSetTheme = (newTheme) => {
        setTheme(newTheme);
        theme.value = newTheme;
    };
    
    const handleToggleTheme = () => {
        const newTheme = toggleTheme();
        theme.value = newTheme;
    };
    
    let cleanup;
    onMounted(() => {
        cleanup = watchSystemTheme((newTheme) => {
            theme.value = newTheme;
        });
    });
    
    onUnmounted(() => {
        cleanup && cleanup();
    });
    
    return {
        theme,
        setTheme: handleSetTheme,
        toggleTheme: handleToggleTheme,
        isDark: computed(() => theme.value === 'dark')
    };
};

3. 主题切换按钮组件 #

javascript
class ThemeToggle {
    constructor(options = {}) {
        this.container = options.container || document.body;
        this.onChange = options.onChange || (() => {});
        this.render();
        this.init();
    }
    
    render() {
        this.element = document.createElement('button');
        this.element.className = 'theme-toggle';
        this.updateIcon();
        this.container.appendChild(this.element);
        
        this.element.addEventListener('click', () => {
            const newTheme = toggleTheme();
            this.updateIcon();
            this.onChange(newTheme);
        });
    }
    
    updateIcon() {
        const theme = getTheme();
        this.element.innerHTML = theme === 'dark' ? '☀️' : '🌙';
        this.element.setAttribute('aria-label', 
            theme === 'dark' ? '切换到浅色模式' : '切换到深色模式'
        );
    }
    
    init() {
        initTheme();
        watchSystemTheme(() => this.updateIcon());
    }
}

new ThemeToggle({
    container: document.getElementById('header'),
    onChange: (theme) => console.log('主题切换:', theme)
});

最佳实践 #

1. 避免闪烁 #

html
<head>
    <script>
        (function() {
            const theme = localStorage.getItem('theme') || 
                (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
            document.documentElement.setAttribute('data-theme', theme);
        })();
    </script>
    <link rel="stylesheet" href="styles.css">
</head>

2. 图片适配 #

css
.logo {
    content: url('logo-light.png');
}

[data-theme="dark"] .logo {
    content: url('logo-dark.png');
}

3. 图表适配 #

javascript
const getChartTheme = () => {
    const isDark = getTheme() === 'dark';
    return {
        backgroundColor: isDark ? '#1a1a1a' : '#ffffff',
        textColor: isDark ? '#ffffff' : '#333333',
        gridColor: isDark ? '#404040' : '#e0e0e0'
    };
};

下一步 #

现在你已经掌握了主题管理,接下来学习 加密哈希,了解 MD5 和 MD6 哈希算法!

最后更新:2026-04-04