黑暗模式切换和偏好配色方案

2025-06-04

黑暗模式切换和偏好配色方案

2021 年,当我在 React 中编写一个可访问的暗黑模式切换功能时, @grahamthedev建议我在主题设置器中实现一个prefers-color-scheme检查。我终于抽出时间实现了。

  1. 什么是prefers-color-scheme
  2. 模拟用户偏好进行测试
  3. prefers-color-scheme使用 JavaScript进行检测
  4. 我的 Toggle 解决方案
  5. 重构

什么是prefers-color-scheme

prefers-color-scheme是一种媒体功能。媒体功能提供有关用户设备或用户代理的信息。用户代理是代表用户的程序,在本例中指的是网络浏览器或操作系统 (OS)。

您可能最熟悉媒体查询中使用的媒体功能,例如响应式 CSS。



@media (max-width: 800px) {
  .container {
    width: 60px;
  }
}


Enter fullscreen mode Exit fullscreen mode

默认值为prefers-color-scheme“亮”。如果用户在其设备或浏览器中明确选择了暗黑模式,prefers-color-scheme则设置为“暗黑”。您可以在媒体查询中使用它来相应地更新样式。



@media (prefers-color-scheme: dark) {
  .theme {
    color: #FFFFFF,
    background-color: #000000
  }
}


Enter fullscreen mode Exit fullscreen mode

模拟用户偏好进行测试

在 Chrome DevTools 中,您可以在渲染选项卡prefers-color-scheme中模拟和其他媒体功能

浅色模式下 chrome DevTools 和 abbeyperini.dev 的屏幕截图

如果您更喜欢 Firefox DevTools,prefers-color-scheme在 CSS 检查器中就有按钮。

prefers-color-scheme使用 JavaScript进行检测

很遗憾,我没有在 CSS 中更改主题。我结合使用了localStorage和替换组件的类名。幸运的是,一如既往,Web API 随时可用。

window.matchMedia将返回一个MediaQueryList具有布尔属性的对象matches。这将适用于任何典型的媒体查询,如下所示prefers-color-scheme



window.matchMedia('(prefers-color-scheme: dark)');


Enter fullscreen mode Exit fullscreen mode

我的 Toggle 解决方案

您可以在我的投资组合仓库中查看此应用程序的所有代码

首先,我需要检查用户是否访问过我的网站,并且localStorage是否已设置“主题”项。接下来,我需要通过 检查用户的偏好设置是否不是暗黑模式prefers-color-scheme。然后,我希望将主题默认设置为暗黑模式。我还需要确保在用户设置初始偏好设置后,切换按钮能够更新主题。

我的主题实用程序文件最终看起来像这样:



function setTheme(themeName, setClassName) {
    localStorage.setItem('theme', themeName);
    setClassName(themeName);
}

function keepTheme(setClassName) {
  const theme = localStorage.getItem('theme');
  if (theme) {
    setTheme(theme, setClassName);
    return;
  }

  const prefersLightTheme = window.matchMedia('(prefers-color-scheme: light)');
  if (prefersLightTheme.matches) {
    setTheme('theme-light', setClassName);
    return;
  }

  setTheme('theme-dark', setClassName);
}

module.exports = {
  setTheme,
  keepTheme
}


Enter fullscreen mode Exit fullscreen mode

我的主要组件调用keepTheme()了它的useEffect,并setClassName来自它的状态。我使用在设置项目useState之前默认使用暗黑模式。localStorage



const [className, setClassName] = useState("theme-dark");


Enter fullscreen mode Exit fullscreen mode

该切换按钮用于setTheme()更新主题。

重构

之前,setTheme()没有使用setClassName



function setTheme(themeName) {
    document.documentElement.className = themeName;
    localStorage.setItem('theme', themeName);
}


Enter fullscreen mode Exit fullscreen mode

由于我使用的是 React,所以我想避免直接操作 DOM。现在,我的主组件在其最外层元素上使用了一个动态类名。



<div className={`App ${className}`}>


Enter fullscreen mode Exit fullscreen mode

setClassName我想在未来的某个时候重构我的组件架构,这可能有助于我减少作为回调传递的次数。

keepTheme()曾经有很多嵌套的条件。



  if (localStorage.getItem('theme')) {
    if (localStorage.getItem('theme') === 'theme-dark') {
      setTheme('theme-dark');
    } else if (localStorage.getItem('theme') === 'theme-light') {
      setTheme('theme-light');
    }
  } else {
    setTheme('theme-dark');
  }


Enter fullscreen mode Exit fullscreen mode

我的本能总是明确地说明else,所以我的下一个解决方案仍然检查了太多东西。我至少开始使用保护条款了。



const theme = localStorage.getItem('theme');
  if (theme) {
    if (theme === 'theme-dark') {
      setTheme('theme-dark');
    } 

    if (theme === 'theme-light') {
      setTheme('theme-light');
    }
    return;
  }

  const prefersDarkTheme = window.matchMedia('(prefers-color-scheme: dark)');
  if (prefersDarkTheme.matches) {
    setTheme('theme-dark');
    return;
  } 

  const prefersLightTheme = window.matchMedia('(prefers-color-scheme: light)');
  if (prefersLightTheme.matches) {
    setTheme('theme-light');
    return;
  }

  setTheme('theme-dark');


Enter fullscreen mode Exit fullscreen mode

此时,我意识到如果我已经默认使用暗黑模式,就不需要检查了(prefers-color-scheme: dark)。然后我了解到localStorage项目与窗口的原点绑定。由于我不需要检查值,所以我只需检查theme是否存在,然后将其传递给即可setTheme()

结论

回到这个切换开关,我有点怀旧。差不多两年前,它帮助我找到了第一份开发者工作。有时候,回顾自己在知识匮乏的时候写的代码,会让人感到很不自在。这次,我已经在做两年后必须做的那种更新了,很高兴看到自己学到了这么多。这让我想要重构应用程序的其余部分,并期待着在接下来的两年里看到自己能学到什么。

文章来源:https://dev.to/abbeyperini/dark-mode-toggle-and-prefers-color-scheme-4f3m
PREV
每次技术面试后我如何获得反馈
NEXT
HTTP 初学者指南 - 第 1 部分:定义