在 Nuxt.js 中配合 windicss 实现暗黑模式适配

在 windicss 中，提供了媒体查询和 class 两种方式实现暗黑模式适配。而且比较省心的是 —— 其提供的 dark 变体会自动根据选择的适配模式，生成对应的代码，可以有效避免写出一堆没用的 css，看起来也比较清晰。

为了方便控制，我们选择使用 class 的方式来切换暗黑模式（即给根元素赋予类名 dark 来切换到暗黑模式）

基础样式

首先，需要一些全局 css 来解决 windicss 无法覆盖的样式。

滚动条颜色改变

正常情况下，你可能会想用 -webkit-scrollbar 伪类，但是，其实有更优雅的写法。浏览器提供了一个 color-scheme css 属性，将其设置为 dark，浏览器便会自动将页面内所有浏览器自带的元素渲染成暗色风格

Copy
html.dark {
color-scheme: dark;
}

图片亮度降低

也很简单，应用一个 filter 就好了

Copy
html.dark img {
filter: brightness(0.8);
}

自动检测

接下来就是重头戏了，如何判断并给 html 元素加上dark类名，毕竟 windicss 可不会帮我们自动处理。

我们会在前端为用户提供一个下拉框，用户可以选择自动适应、保持暗黑模式、保持明亮模式

为了避免页面初载入时样式切换导致的闪屏，最终决定将该配置储存到 cookie 而非 localstorage 中，这样能够发挥 ssr 的作用，当用户强制暗黑 / 明亮时，服务端就能将类名写入 html 标签中。

Copy
function readDarkModeInStorage() {
const darkMode = useCookie('darkMode')
const possibleValues = ['auto', 'dark', 'light']
if (darkMode.value && possibleValues.includes(darkMode.value)) {
return darkMode.value
} else {
return 'auto'
}
}

上面是一个辅助函数，用于从储存中读出暗黑模式配置（服务端 / 客户端均可用）

Copy
function setModeClass(isDark: boolean): void {
if (isDark) {
useHead({
htmlAttrs: { class: 'dark' },
meta: [{ name: 'theme-color', content: '#121212' }],
})
} else {
useHead({
htmlAttrs: { class: '' },
meta: [{ name: 'theme-color', content: '#ffffff' }],
})
}
}

一个用于设置暗黑模式样式的工具函数，当传入布尔值时，会同时设置 html 的类名和 theme-color 的 meta 标签（ssr/csr 均可用）

使用了来自 VueUse 的 useHead 方法

Copy
const currentMode = ref(readDarkModeInStorage())
const preferredDark = usePreferredDark()

watchEffect(() => {
if (currentMode.value === 'auto') {
if (preferredDark.value) {
setModeClass(true)
} else {
setModeClass(false)
}
} else if (currentMode.value === 'dark') {
setModeClass(true)
} else if (currentMode.value === 'light') {
setModeClass(false)
}

useCookie('darkMode').value = currentMode.value
})

这里便是关键，首先，读入配置并初始化到 currentMode 变量，接着，使用 VueUse 提供的 usePreferredDark 来获取当前浏览器的颜色模式。

使用一个监听副作用的函数，当上面两个值发生改变时，调用 setModeClass 工具函数去完成最终的类名修改，并将配置写入 cookie。