暗黑模式设计完整方案

HTMLPAGE 团队
20 分钟阅读

学习暗黑模式实现、色彩方案、对比度管理和最佳实践

暗黑模式设计完整方案

暗黑模式已成为现代应用的标准功能。它能够减少眼睛疲劳、节省电池、改善用户体验。

核心色彩系统

Light Mode 配色

:root {
  /* Light Mode */
  --bg-primary: #ffffff;
  --bg-secondary: #f5f5f5;
  --bg-tertiary: #efefef;
  
  --text-primary: #1a1a1a;
  --text-secondary: #666666;
  --text-tertiary: #999999;
  
  --border-color: #e0e0e0;
  --divider-color: #f0f0f0;
}

Dark Mode 配色

@media (prefers-color-scheme: dark) {
  :root {
    /* Dark Mode */
    --bg-primary: #1a1a1a;
    --bg-secondary: #2d2d2d;
    --bg-tertiary: #3a3a3a;
    
    --text-primary: #ffffff;
    --text-secondary: #e0e0e0;
    --text-tertiary: #a0a0a0;
    
    --border-color: #404040;
    --divider-color: #2a2a2a;
  }
}

实现方案

方案 1:prefers-color-scheme

/* 自动跟随系统设置 */
@media (prefers-color-scheme: light) {
  :root {
    --bg: #fff;
    --text: #000;
  }
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #1a1a1a;
    --text: #fff;
  }
}

body {
  background: var(--bg);
  color: var(--text);
}

方案 2:JavaScript 切换

// 检测和切换暗黑模式
function initDarkMode() {
  const isDark = localStorage.getItem('darkMode') === 'true' ||
                 window.matchMedia('(prefers-color-scheme: dark)').matches;
  
  if (isDark) {
    document.documentElement.setAttribute('data-theme', 'dark');
  }
}

function toggleDarkMode() {
  const isDark = document.documentElement.getAttribute('data-theme') === 'dark';
  const newTheme = isDark ? 'light' : 'dark';
  
  document.documentElement.setAttribute('data-theme', newTheme);
  localStorage.setItem('darkMode', newTheme === 'dark');
}

// CSS 应用
html[data-theme='light'] {
  color-scheme: light;
}

html[data-theme='dark'] {
  color-scheme: dark;
}

方案 3:CSS Variables + JavaScript

const themes = {
  light: {
    '--bg-primary': '#ffffff',
    '--text-primary': '#000000',
    '--accent': '#0066cc',
    '--border': '#e0e0e0',
  },
  dark: {
    '--bg-primary': '#1a1a1a',
    '--text-primary': '#ffffff',
    '--accent': '#4da3ff',
    '--border': '#404040',
  },
};

function applyTheme(themeName) {
  const theme = themes[themeName];
  Object.entries(theme).forEach(([key, value]) => {
    document.documentElement.style.setProperty(key, value);
  });
  localStorage.setItem('theme', themeName);
}

对比度管理

/* Light Mode 对比度 */
:root {
  --contrast-high: #000000;     /* 21:1 */
  --contrast-medium: #333333;   /* 12.6:1 */
  --contrast-low: #666666;      /* 5.1:1 */
}

/* Dark Mode 对比度 */
@media (prefers-color-scheme: dark) {
  :root {
    --contrast-high: #ffffff;    /* 21:1 */
    --contrast-medium: #e0e0e0;  /* 11.6:1 */
    --contrast-low: #a0a0a0;     /* 4.5:1 */
  }
}

/* 应用对比度 */
.text-primary { color: var(--contrast-high); }
.text-secondary { color: var(--contrast-medium); }
.text-tertiary { color: var(--contrast-low); }

图片和图表处理

<!-- 针对不同主题的图片 -->
<picture>
  <source 
    media="(prefers-color-scheme: dark)" 
    srcset="chart-dark.svg"
  />
  <img src="chart-light.svg" alt="图表" />
</picture>

<!-- SVG 颜色适配 -->
<svg class="icon">
  <circle cx="50" cy="50" r="40" fill="currentColor" />
</svg>

<style>
  .icon {
    color: var(--text-primary);
  }
</style>

完整示例

import { useState, useEffect } from 'react';

function ThemeProvider({ children }) {
  const [theme, setTheme] = useState('light');
  const [mounted, setMounted] = useState(false);
  
  useEffect(() => {
    setMounted(true);
    
    // 获取保存的主题或系统偏好
    const savedTheme = localStorage.getItem('theme');
    const systemTheme = window.matchMedia('(prefers-color-scheme: dark)').matches 
      ? 'dark' 
      : 'light';
    
    const initialTheme = savedTheme || systemTheme;
    setTheme(initialTheme);
    document.documentElement.setAttribute('data-theme', initialTheme);
  }, []);
  
  const toggleTheme = () => {
    const newTheme = theme === 'light' ? 'dark' : 'light';
    setTheme(newTheme);
    localStorage.setItem('theme', newTheme);
    document.documentElement.setAttribute('data-theme', newTheme);
  };
  
  // 防止闪烁
  if (!mounted) {
    return null;
  }
  
  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      {children}
      <ThemeToggle theme={theme} onChange={toggleTheme} />
    </ThemeContext.Provider>
  );
}

function ThemeToggle({ theme, onChange }) {
  return (
    <button 
      onClick={onChange}
      aria-label={`切换到${theme === 'light' ? '暗黑' : '亮色'}模式`}
    >
      {theme === 'light' ? '🌙' : '☀️'}
    </button>
  );
}

最佳实践

应该做的事:

  • 支持系统偏好
  • 提供手动切换选项
  • 确保足够的对比度
  • 优化图片和图表
  • 防止加载闪烁

不应该做的事:

  • 强制单一模式
  • 忽视性能影响
  • 使用相同的颜色
  • 忘记保存用户偏好
  • 过度使用深色背景

测试清单

  • 在浅色和深色模式下测试所有页面
  • 检查颜色对比度符合 WCAG 标准
  • 验证图片和图表在两种模式下清晰
  • 测试主题切换的平滑性
  • 检查用户偏好是否被保存