📖 核心概念
- 暗色模式设计原则
- CSS自定义属性切换
- prefers-color-scheme媒体查询
- Three-layer颜色系统
- 图片与阴影的暗色适配
- 平滑过渡与闪烁防止
💻 代码实现
暗色模式实现
✅ css
/* ===== 三层颜色系统 ===== */
/* 层1: 原始色值 */
:root {
--gray-50: #f8fafc; --gray-100: #f1f5f9; --gray-900: #0f172a;
--cyan-500: #06b6d4; --cyan-600: #0891b2;
}
/* 层2: 语义Token(亮色) */
:root {
--color-bg-base: var(--gray-50);
--color-bg-surface: white;
--color-bg-elevated: var(--gray-100);
--color-bg-sunken: var(--gray-100);
--color-text-primary: var(--gray-900);
--color-text-secondary: #64748b;
--color-border-default: #e2e8f0;
--color-accent: var(--cyan-500);
--color-accent-hover: var(--cyan-600);
--shadow-card: 0 1px 3px rgba(0,0,0,0.1);
--shadow-elevated: 0 10px 25px rgba(0,0,0,0.1);
}
/* 层2: 语义Token(暗色) */
[data-theme="dark"] {
--color-bg-base: var(--gray-900);
--color-bg-surface: #1e293b;
--color-bg-elevated: #334155;
--color-bg-sunken: #020617;
--color-text-primary: #f1f5f9;
--color-text-secondary: #94a3b8;
--color-border-default: #334155;
--color-accent: var(--cyan-500);
--color-accent-hover: var(--cyan-400);
--shadow-card: 0 1px 3px rgba(0,0,0,0.3);
--shadow-elevated: 0 10px 25px rgba(0,0,0,0.4);
}
/* 自动跟随系统 */
@media (prefers-color-scheme: dark) {
:root:not([data-theme]) {
--color-bg-base: var(--gray-900);
--color-bg-surface: #1e293b;
/* ... 暗色值 */
}
}
/* ===== 防止闪烁 ===== */
/* 在<head>中内联此脚本 */
/*
<script>
(function() {
const stored = localStorage.getItem('theme');
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
const theme = stored || (prefersDark ? 'dark' : 'light');
document.documentElement.setAttribute('data-theme', theme);
})();
</script>
*/
React暗色模式系统
✅ tsx
import { useEffect, useState, useCallback } from 'react';
type Theme = 'light' | 'dark' | 'system';
type ResolvedTheme = 'light' | 'dark';
function useTheme() {
const [theme, setThemeState] = useState<Theme>(() => {
if (typeof window === 'undefined') return 'system';
return (localStorage.getItem('theme') as Theme) || 'system';
});
const resolvedTheme: ResolvedTheme = (() => {
if (theme !== 'system') return theme;
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
})();
const setTheme = useCallback((newTheme: Theme) => {
setThemeState(newTheme);
localStorage.setItem('theme', newTheme);
const resolved = newTheme === 'system'
? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light')
: newTheme;
document.documentElement.setAttribute('data-theme', resolved);
}, []);
// 监听系统主题变化
useEffect(() => {
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
const handler = (e: MediaQueryListEvent) => {
if (theme === 'system') {
document.documentElement.setAttribute('data-theme', e.matches ? 'dark' : 'light');
}
};
mediaQuery.addEventListener('change', handler);
return () => mediaQuery.removeEventListener('change', handler);
}, [theme]);
return { theme, resolvedTheme, setTheme };
}
// 主题切换按钮
function ThemeToggle() {
const { resolvedTheme, setTheme } = useTheme();
const isDark = resolvedTheme === 'dark';
return (
<button
onClick={() => setTheme(isDark ? 'light' : 'dark')}
className="theme-toggle"
aria-label={isDark ? '切换到亮色模式' : '切换到暗色模式'}
>
{/* 太阳/月亮图标,带平滑过渡 */}
<div className="theme-icon" style={{ transform: isDark ? 'rotate(0)' : 'rotate(180deg)' }}>
{isDark ? '🌙' : '☀️'}
</div>
</button>
);
}
/* 暗色模式图片适配 */
/* .dark-image { display: none; }
[data-theme="dark"] .light-image { display: none; }
[data-theme="dark"] .dark-image { display: block; }
/* 暗色模式阴影调整 */
[data-theme="dark"] .card {
box-shadow: 0 1px 3px rgba(0,0,0,0.4); /* 暗色下更强的阴影 */
border: 1px solid var(--color-border-default); /* 暗色下用边框代替阴影 */
} */
🎯 练习任务
- 1 实现无闪烁的暗色模式切换系统
- 2 设计三层颜色Token体系,支持亮暗主题
- 3 实现暗色模式下的图片自适应
- 4 添加平滑的主题切换过渡动画
🏆 成就解锁
暗色模式专家 — 掌握暗色模式实现技巧,打造优雅的双主题体验