Back

如何使用 CSS 和 JavaScript 构建深色模式切换功能

如何使用 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);
}

该函数根据当前激活的配色方案自动选择适当的颜色,减少代码重复。

用于切换和持久化的 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 降级方案。

测试您的实现

在不同场景下测试您的深色模式切换功能:

  1. 清除 localStorage 并验证系统偏好设置检测是否正常工作
  2. 切换主题并刷新页面以确认持久化功能
  3. 在网站打开时更改系统偏好设置
  4. 使用浏览器开发者工具检查对比度
  5. 测试键盘导航和屏幕阅读器播报

总结

一个实现良好的深色模式解决方案应该尊重用户偏好、持久化选择,并利用现代 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..

OpenReplay