【笔记】web 黑夜模式通用适配方案

一、判断黑夜模式的方式

1.1 CSS 媒体查询判断黑夜模式的属性——prefers-color-scheme

prefers-color-scheme CSS 媒体特性用于检测用户是否有将系统的主题色设置为亮色或者暗色。 ——MDN

值:

  • no-preference:表示系统未得知用户在这方面的选项。在布尔值上下文中,其执行结果为 false。
  • light:表示用户已告知系统他们选择使用浅色主题的界面。
  • dark:表示用户已告知系统他们选择使用暗色主题的界面。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/* Light mode */
@media (prefers-color-scheme: light) {
html,
body {
color: black;
background-color: white;
}
}

/* Dark mode */
@media (prefers-color-scheme: dark) {
html,
body {
color: white;
background-color: black;
}
}

1.1.1 兼容情况

除了 IE 外,其他主流浏览器基本都支持prefers-color-scheme

p-1.png

1.2 JavaScript 判断黑夜模式的属性——matchMedia

Window 的 matchMedia() 方法返回一个新的 MediaQueryList 对象,表示指定的媒体查询字符串解析后的结果。返回的 MediaQueryList 可被用于判定 Document 是否匹配媒体查询,或者监控一个 document 来判定它匹配了或者停止匹配了此媒体查询。 ——MDN

window.matchMedia(xxx) 返回一个 listenable-like 对象 MediaQueryList, 它继承自 EventTarget, 这意味着可以通过直接它获得最新的 MediaQuery 检测情况:

1
2
3
4
5
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
// Dark mode
} else {
// Light mode
}

1.2.1 监听主题变化

与 CSS 媒体查询不同,matchMedia 我们需要监听主题的变化作出对应处理,如下所示:

1
2
3
4
5
6
7
8
9
const darkModeQuery = window.matchMedia('(prefers-color-scheme: dark)');

darkModeQuery.addListener(e => {
if (e.matches) {
// Dark mode
} else {
// Light mode
}
});

1.2.2 兼容情况

主流浏览器基本都支持matchMedia

p-2.png

二、黑夜模式样式适配处理方案

2.1 CSS 纯媒体查询实现

适用场景:需要快速实现、无需用户自定义主题的项目。如

1
2
3
4
5
6
7
@media (prefers-color-scheme: dark) {
body {
background-color: #1a1a1a;
color: #e0e0e0;
}
/* 其他元素深色样式 */
}

优缺点分析

优点:

  • 零 js 依赖,纯 css 实现
  • 系统级实时响应(跟随系统设置即时切换)
  • 维护成本最低

缺点:

  • 不能保存用户偏好(比如用户想覆盖系统设置)

2.2 CSS 变量 + 媒体查询

适用场景:中大型项目、需要多主题扩展、已使用 CSS 变量的代码库。

如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
:root {
--bg-color: #ffffff;
--text-color: #333333;
}

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

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

优缺点分析

优点:

  • 集中管理颜色变量
  • 方便扩展多主题
  • 支持渐进增强

缺点:

  • 仍无法保存用户偏好
  • 需要统一使用 CSS 变量规范

2.3 js matchMedia 动态切换 + 本地存储

适用场景:需要用户自定义主题的 ToC 产品、重视用户体验的 Web 应用。

如:

1
<button id="themeToggle">切换主题</button>
1
2
3
4
5
6
7
8
9
body.light-mode {
--bg-color: #ffffff;
--text-color: #333333;
}

body.dark-mode {
--bg-color: #1a1a1a;
--text-color: #e0e0e0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const themeToggle = document.querySelector('#themeToggle');
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)');

// 初始化主题
function initTheme() {
const savedTheme = localStorage.getItem('theme') || (prefersDark.matches ? 'dark' : 'light');
document.body.classList.add(`${savedTheme}-mode`);
}

// 切换主题
themeToggle.addEventListener('click', () => {
document.body.classList.toggle('dark-mode');
document.body.classList.toggle('light-mode');
const currentTheme = document.body.classList.contains('dark-mode') ? 'dark' : 'light';
localStorage.setItem('theme', currentTheme);
});

// 监听系统变化
prefersDark.addListener(e => {
if (!localStorage.getItem('theme')) {
// 只在用户未手动选择时响应系统
document.body.classList.toggle('dark-mode', e.matches);
}
});

优缺点分析

优点:

  • 支持用户偏好保存
  • 同时响应系统和手动切换
  • 最佳用户体验

缺点:

  • 需要维护两套样式,依赖 js 处理。实现复杂度最高

2.4 js matchMedia 动态切换 + CSS-in-JS 运行时方案

适用场景:适合 React 等框架,已使用 CSS-in-JS 方案

如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 使用styled-components示例
import { createGlobalStyle, ThemeProvider } from 'styled-components';

const lightTheme = { bg: '#fff', text: '#333' };
const darkTheme = { bg: '#1a1a1a', text: '#e0e0e0' };

const GlobalStyle = createGlobalStyle`
body {
background: ${props => props.theme.bg};
color: ${props => props.theme.text};
}
`;

function App() {
const [isDark, setIsDark] = useState(window.matchMedia('(prefers-color-scheme: dark)').matches);

useEffect(() => {
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
const handler = e => setIsDark(e.matches);
mediaQuery.addListener(handler);
return () => mediaQuery.removeListener(handler);
}, []);

return (
<ThemeProvider theme={isDark ? darkTheme : lightTheme}>
<GlobalStyle />
{/* 页面内容 */}
</ThemeProvider>
);
}

优缺点分析

优点:

  • 完美配合组件化开发
  • 主题状态可全局管理
  • 支持动态主题切换

缺点:

  • 强依赖特定框架
  • 需要 CSS-in-JS 体系支持

2.5 *CSS 媒体查询 + filter 滤镜处理

适合场景:要求最快上线、或最小改动。本质其实也归属于方案 1(2.1)

如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/* 整个页面增加滤镜 */
@media (prefers-color-scheme: dark) {
html {
filter: invert(1) hue-rotate(180deg);
}

/* 图片、视频等元素不需要处理 */
img,
video,
.logo,
.icon {
filter: invert(1) hue-rotate(180deg);
}
}

滤镜设置解释

整体滤镜处理可以概括为:反色 + 调整色相

  • invert(1): invert() 函数用于反转输入图像中的颜色。参数定义了转换的程度。如果参数是 1(或者 100%),则会完全反转颜色,即每个颜色通道的值都会被替换为其补色。例如:黑色变成白色,白色变为黑色等。
    • 当使用 invert(1) 时,则表示将图像的颜色彻底反转,即是:黑色变成白色,白色变为黑色。
  • hue-rotate(180deg): hue-rotate() 函数按照给定的角度旋转色彩轮上的颜色,其实就是冲淡颜色。这里的“角度”是指在标准色轮上转动多少度。色轮是一个圆形图表,显示了不同颜色如何根据它们的色调相互关联。
    • 当使用 hue-rotate(180deg) 时,意味着所有颜色都会在其原始位置基础上沿着色轮顺时针方向移动 180 度。比如红色会变成青色、绿色变成洋红色、蓝色变成黄色等,因为这些是在色轮上相对的颜色。

更多信息可见MDN - invertMDN - hue-rotate

兼容情况

除 IE 外,主流浏览器基本能支持。

p-3.png

优缺点分析

优点:

  • 开发和维护成本都是最低
  • 零 js 依赖,纯 css 实现
  • 系统级实时响应(跟随系统设置即时切换)

缺点:

  • 不能或很难自定义色值
  • 不能保存用户偏好(比如用户想覆盖系统设置)

三、黑夜模式色值设计标准

黑夜模式的色值设计和黑夜模式的工程实现没啥关系,属于用户体验保障。

3.1 WCAG 标准

WCAG(Web Content Accessibility Guidelines) 是由 W3C 制定的国际无障碍标准。其中颜色设置也是一项重要标准。

法律意义:WCAG 在美国《ADA 法案》、欧盟《EN 301 549》等法规中被引用

一些具体要求

  1. 核心要求
元素类型 最小对比度 例外场景
普通文本(<18pt/24px) 4.5:1 装饰性/禁用状态文本
大文本(≥18pt/24px) 3:1 粗体大文本(≥14pt/18.5px 粗体)
图形控件(图标/按钮) 3:1 纯装饰性图形
  1. 计算公式

使用相对亮度(Luminance)公式:

1
Contrast Ratio = (L1 + 0.05) / (L2 + 0.05)

其中:

  • L1 = 较亮颜色的相对亮度(0~1)
  • L2 = 较暗颜色的相对亮度(0~1)

实际开发中的应用

  1. 颜色选择示例
场景 通过示例 失败示例
白底黑字 #FFFFFF vs #000000 (21:1) #FFFFFF vs #666666 (5.74:1)
深色模式 #1A1A1A vs #E0E0E0 (10.3:1) #333333 vs #999999 (3.31:1)
  1. 调试工具

自动检测:

1
2
3
4
5
# Chrome DevTools
Styles面板 → 点击颜色选择器 → 显示对比度比率

# VS Code插件
"WCAG Color Contrast Checker"

在线工具:

  1. 设计技巧
1
2
3
4
5
6
7
8
9
10
11
/* 安全配色方案示例 */
:root {
--safe-dark-bg: #1a1a1a; /* 避免纯黑(#000) */
--safe-light-text: #e0e0e0; /* 避免纯白(#fff) */
--accent-color: #007bff; /* 品牌色需单独验证 */
}

/* 禁用状态处理 */
button:disabled {
opacity: 0.6; /* 需重新验证对比度 */
}

深度注意事项

  1. 动态场景:
  • 悬停/聚焦状态的对比度需保持合规
  • 渐变/阴影覆盖区域需取最差值验证
  1. 字体特性:
  • 300 以下字重需提高对比度要求
  • 衬线字体可能需要额外对比度补偿
  1. 环境适配:
  • 移动设备户外模式需考虑屏幕反光影响
  • OLED 屏幕需验证 PWM 调光下的可读性

法律合规案例

  • Target 诉讼案(2006):因对比度不足赔偿$6 百万
  • Domino’s 披萨案(2019):网站对比度违规败诉
  • 英国政府:强制要求所有公共网站通过 AA 认证

进阶建议

1.AAA 级目标:

  • 普通文本:7:1
  • 大文本:4.5:1
  1. 无障碍测试:
1
2
3
4
5
# 使用屏幕阅读器验证
NVDA (Windows) / VoiceOver (Mac)

# 自动化工具
axe DevTools / Lighthouse

设计系统集成:

1
2
3
4
5
6
7
8
9
10
11
// Storybook等工具集成对比度检测
addon-a11y: {
config: {
contrast: {
thresholds: {
AA: 4.5,
AAA: 7
}
}
}
}

通过严格遵循这些标准,可确保您的深色模式既美观又合规。建议将对比度检测纳入 CI/CD 流程,实现自动化保障。


相关链接