如何使用 CSS 和 JavaScript 构建深色模式切换功能
深色模式已成为现代网站的必备功能。用户期望能够根据环境、时间或个人偏好切换主题。本教程将向您展示如何构建一个强大的深色模式切换功能,该功能能够尊重系统偏好设置、记住用户选择,并利用现代 CSS 特性实现最佳性能。
核心要点
- 构建一个尊重系统偏好设置并持久化用户选择的深色模式切换功能
- 使用 CSS 自定义属性和 color-scheme 属性实现高效主题切换
- 实现 JavaScript 以防止页面加载时出现无样式内容闪烁
- 应用可访问性最佳实践,支持键盘导航和屏幕阅读器
设置 HTML 结构
从包含切换按钮并声明初始主题的语义化 HTML 开始:
<!DOCTYPE html>
<html lang="en" data-theme="light">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="color-scheme" content="light dark">
<title>Dark Mode Toggle Demo</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<button type="button" id="theme-toggle" aria-label="Toggle dark mode">
<span class="toggle-text">Dark</span>
</button>
<!-- Your content here -->
</body>
</html>
HTML 元素上的 data-theme 属性控制我们的主题。color-scheme meta 标签告诉浏览器调整原生 UI 元素(如滚动条和表单控件)的样式。
使用现代特性实现 CSS
使用 CSS 自定义属性和 color-scheme
使用 CSS 自定义属性定义颜色标记,然后利用 CSS color-scheme 属性实现原生元素主题化:
:root {
color-scheme: light dark;
/* Light theme colors (default) */
--bg-color: #ffffff;
--text-color: #213547;
--accent-color: #0066cc;
}
[data-theme="dark"] {
--bg-color: #1a1a1a;
--text-color: #e0e0e0;
--accent-color: #66b3ff;
}
body {
background-color: var(--bg-color);
color: var(--text-color);
transition: background-color 0.3s ease, color 0.3s ease;
}
使用 prefers-color-scheme 尊重系统偏好设置
添加对 prefers-color-scheme 的支持以自动匹配系统设置:
@media (prefers-color-scheme: dark) {
:root:not([data-theme="light"]) {
--bg-color: #1a1a1a;
--text-color: #e0e0e0;
--accent-color: #66b3ff;
}
}
现代的 light-dark() 函数
对于支持 light-dark() 函数的浏览器,您可以简化颜色定义:
:root {
color-scheme: light dark;
}
body {
background-color: light-dark(#ffffff, #1a1a1a);
color: light-dark(#213547, #e0e0e0);
}
该函数根据当前激活的配色方案自动选择适当的颜色,减少代码重复。
Discover how at OpenReplay.com.
用于切换和持久化的 JavaScript
防止页面加载时的主题闪烁
将此脚本紧接在 <body> 开始标签之后,以防止无样式内容闪烁:
<script>
// Apply saved theme immediately to prevent flash
(function() {
const saved = localStorage.getItem('theme');
if (saved) {
document.documentElement.setAttribute('data-theme', saved);
} else if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
document.documentElement.setAttribute('data-theme', 'dark');
}
})();
</script>
完整的深色模式实现
添加以下 JavaScript 实现切换功能:
const toggle = document.getElementById('theme-toggle');
const html = document.documentElement;
// Get current theme
function getTheme() {
return html.getAttribute('data-theme') ||
(window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
}
// Set theme and update UI
function setTheme(theme) {
html.setAttribute('data-theme', theme);
localStorage.setItem('theme', theme);
updateToggleText(theme);
}
// Update button text
function updateToggleText(theme) {
const text = toggle.querySelector('.toggle-text');
text.textContent = theme === 'dark' ? 'Light' : 'Dark';
toggle.setAttribute('aria-label', `Switch to ${theme === 'dark' ? 'light' : 'dark'} mode`);
}
// Initialize
updateToggleText(getTheme());
// Handle toggle click
toggle.addEventListener('click', () => {
const current = getTheme();
setTheme(current === 'dark' ? 'light' : 'dark');
});
// Listen for system preference changes
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => {
if (!localStorage.getItem('theme')) {
setTheme(e.matches ? 'dark' : 'light');
}
});
可访问性和最佳实践
必要的可访问性功能
您的深色模式切换功能必须支持键盘访问并正确标记:
#theme-toggle:focus-visible {
outline: 2px solid var(--accent-color);
outline-offset: 2px;
}
确保两种主题都具有足够的颜色对比度。深色模式并不意味着低对比度——至少要保持 WCAG AA 标准(普通文本 4.5:1)。
应避免的做法
不要在整个页面上使用 filter: invert(1)——这会不必要地反转图像和媒体内容。避免在深色模式中使用纯黑色 (#000000) 背景。使用深灰色 (#1a1a1a) 以获得更好的可读性。永远不要仅依赖 JavaScript 而不提供 CSS 降级方案。
测试您的实现
在不同场景下测试您的深色模式切换功能:
- 清除 localStorage 并验证系统偏好设置检测是否正常工作
- 切换主题并刷新页面以确认持久化功能
- 在网站打开时更改系统偏好设置
- 使用浏览器开发者工具检查对比度
- 测试键盘导航和屏幕阅读器播报
总结
一个实现良好的深色模式解决方案应该尊重用户偏好、持久化选择,并利用现代 CSS 特性。通过结合 color-scheme 属性、自定义属性和最少的 JavaScript,您可以创建一个高性能、可访问且用户友好的主题切换器。light-dark() 函数为现代浏览器简化了颜色管理,同时降级方案确保了兼容性。
请记住:最好的深色模式实现对用户来说是无感的——它只是完全按照用户期望的方式工作。
常见问题
可以,您可以使用 CSS 媒体查询配合 prefers-color-scheme 来检测系统偏好设置。但是,如果需要保存用户偏好并提供可覆盖系统设置的手动切换按钮,就需要使用 JavaScript。
这是因为 JavaScript 在页面渲染后才运行。在 body 标签之后立即放置一个脚本,在页面绘制之前检查 localStorage 并应用保存的主题,可以防止这种闪烁。
不应该,纯黑色可能导致眼睛疲劳并使文本更难阅读。应使用深灰色,如 #1a1a1a 或 #121212。这些颜色在保持良好对比度以满足可访问性要求的同时,减少了视觉疲劳。
避免对图像使用 filter invert。相反,应提供图像的深色模式替代版本,或使用在两种背景下都能正常显示的透明 PNG。对于复杂的图形,可以考虑使用 CSS 遮罩或带有 currentColor 的 SVG。
Truly understand users experience
See every user interaction, feel every frustration and track all hesitations with OpenReplay — the open-source digital experience platform. It can be self-hosted in minutes, giving you complete control over your customer data. . Check our GitHub repo and join the thousands of developers in our community..