深色模式
11/11/2024

一开始我的理解是如果我有需求在操作系统上使用深色模式,那我的网页也应该跟随这个设置,也就是说网页的颜色模式应该使用 CSS 的媒体查询来实现,这是最简单也是最合理的方式。考虑到想更多的了解 UI 设计,我需要经常切换主题来查看我做出来的东西在不同颜色模式下的效果,我还是需要有一种手动控制网页颜色模式的方式,而不是打开 Raycast 输入命令然后切换。于是问题变成如果使用 JavaScript 来完全控制颜色模式,那我的系统在深色模式时打开网页会因为 JavaScript 的加载时间而出现闪白,这不行,这需要被解决。

首先是使用 JavaScript 控制,一般来说我们会使用继承自不同的 CSS 选择器的方式来设置元素颜色,然后通过 JavaScript 设置父元素的选择器从而控制颜色:

.light  element { color: #000; }
.dark   element { color: #fff; }
document.documentElement.classList.replace('light', 'dark')

然后是 CSS 媒体查询,这也是我所知唯一一种不会让网页出现闪黑或闪白的方式,它会跟随系统的颜色模式:

html { --text-primary: #000; }

@media (prefers-color-scheme: dark) {
  html { --text-primary: #fff; }
}

.text-primary { color: var(--text-primary); }

现在我们要结合这两种方式:在没有设置特定的选择器控制颜色时使用媒体查询,否则就使用选择器控制:

/* 在 .light 或默认时设置为浅色模式的文字颜色 */
html, html.light { --text-primary: #000; }

/* 在 .dark 时设置深色模式的文字颜色 */
html.dark  { --text-primary: #fff; }

/* 在深色的系统设置下 */
@media (prefers-color-scheme: dark) {
  /* 且不是 .light 或 .dark 时设置深色模式的文字颜色 */
  html:not(:is(.light, .dark)) { --text-primary: #fff; }
}

/* 调用定义的颜色 */
.text-primary { color: var(--text-primary); }

这样当我们设置html标签的class属性就可以完成颜色的切换,然后注意到我们需要写两遍深色模式下的颜色,因为其中一次需要被包在媒体查询里,如果不使用 CSS 预处理器这会让代码非常啰嗦同时也难以管理;既然现在大家都在使用原子化 CSS 来开发,以 UnoCSS 举例,反正我的颜色本身就是在 UnoCSS 里定义的,那就干脆写一个 Preset

// uno.config.ts

import { defineConfig } from 'unocss'
import presetColors from 'unocss-preset-colors'

export default defineConfig({
  presets: [
    presetColors({
      colors: {
        'text-primary': { light: '#000', dark: '#fff' }
      }
    })
  ]
})
<div class="text-primary">Hello World</div>

获得这样一段 CSS 代码,这里兼顾了透明度设置:

/* layer: preflights */
html,html.light{--text-primary:0 0 0;}
html.dark{--text-primary:242 242 242;}
@media (prefers-color-scheme: dark){html:not(:is(.light,.dark)){--text-primary:242 242 242;}

/* layer: default */
.text-primary{color:rgb(var(--text-primary) / 1);}

OK 结束,放几个按钮给你试试

深色
浅色
系统
CC BY-NC-SA 4.0 © Jiakun Zhao