一种易于实现的深色模式: vue3+windicss

561 阅读2分钟

1,5!哥们在这儿给你写文章

深色模式在现在算是一个刚需了,不管是ubuntu还是windows,系统层面都开始强调“深色”这个概念。 本文的实现前提: vue3&windicss

Q1: 为什么用windicss ?

A: 因为windicss虽然处于没有维护的尴尬场景,但是也没有什么bug,而且很好的完成了tailwind没有的功能:attributy模式。比如说一个分页组件代码如下:

<!-- windicss attributy模式 -->
<div display="flex" justify="center" align="items-center">
    <n-pagination text="center" m="y-4" v-model:page="page" :page-count="10" />
</div>

笔者觉得语义化是很足的,至少比拖到下面看css直观,也比class里一坨的字符串好看多了(纯纯个人观点,不要)。举个例子,里面的代码未必准确)

<div class="display-flex justify-center align-items-center">
    <n-pagination  class="text-center m-y-4" v-model:page="page" :page-count="10" />
</div>

当然萝卜白菜各有所爱,不要强求别人授受自己的风格。

正菜开始。

先看最最最普通的解决方案。

@media (prefers-color-scheme: dark) {
  body {
    background: #222;
    color: #eee;
  }
}
@media (prefers-color-scheme: ligth) {
  body {
    background: #eee;
    color: #222;
  }
}

写两套css,获取系统的颜色方案:是否是深色模式。然后自动集成。 这样有个坏处,用户无法手动切换模式,只能是根据系统方案来。深色就是深色,绝对不会变成亮色模式。在实际操作中,还有一个缺点,那是样式中再切割出颜色和布局,debug会很烦。

进阶解决方案

  1. 获取系统当前方案
let isDarkMode = window.matchMedia('(prefers-color-scheme: dark)').matches;

这样就可以获取系统方案了。那css要怎么组织呢?请出windicss。 windicss有两个方案选择深色模式,一种是根据上文中的(prefers-color-scheme: dark),这像就提到了上文的坏处:只能是根据系统方案来。深色就是深色,绝对不会变成亮色模式。那我们只能选另一种模式:根据<html></html>标签上的class名来决定,如深色为:

<html class="dark">
   <body></body>
</html>

这一步要js来制作:

if (!isDarkMode) {
document.documentElement.classList.remove('dark')
document.documentElement.classList.add('light')
} else {
document.documentElement.classList.remove('light')
document.documentElement.classList.add('dark')
}

接着改windicss配置:

import { defineConfig } from 'windicss/helpers'
export default defineConfig({
  attributify: true,
  darkMode: 'class'
})
  1. 用windicss 组织代码 将dark模式与light模式混在一起,如果dark与light模式的样式不一样,在前面加个dark:,代码如下:
<div w="[100%]" 
 style="--dark-border-color: rgba(255, 255, 255, 0.09);--light-border-color: rgb(239, 239, 245);" 
 text="[var(--light-color)] dark:[var(--dark-color)]" bg="[var(--light-bg)] dark:[var(--dark-bg)]"
 border=" b-[1px] solid l-[var(--light-border-color)] dark:l-[var(--dark-border-color)]"
 p="x-5"
 h="[60px]" 
 display="flex" flex="row" 
 justify="between" 
 align="items-center"
>
 <div display="flex" align="items-center">
  <NIcon mr="0.1rem" size="30" color="#0e7a0d">
   <SearchCircleSharp />
  </NIcon>
  <span text="xl" font="bold leading-3">一个工具</span>
 </div>
 <ThemeButton></ThemeButton>
</div>

其中,<ThemeButton></ThemeButton>是主题切换,下一步再讲。看看效果吧:

light

dark

  1. 手动主题切换

说直白一点,就是1. 中将静态的bool值升级成响应式就行了: 存储一下当前的主题是dark还是light,这个可以放在vuex中完成,这样有些地方可以监听微小的变化,比如上图中的图标。然后再手动改相应的bool值就行了。可以在app.vue中放相应代码:

watch(
    () => store.theme,
    (newer) => {
        if (newer === 'light') {
            document.documentElement.classList.remove('dark')
            document.documentElement.classList.add('light')
        } else {
            document.documentElement.classList.remove('light')
            document.documentElement.classList.add('dark')
        }
    },
    { immediate: true }
)

然后这个主题切换的按钮(我的组件库是naive-ui):

<template>
    <n-button text @click="switchTheme" font="bold">
    <template #icon>
      <n-icon>
        <SunnyOutline v-if="store.theme === 'light'" />
        <MoonOutline v-else />
      </n-icon>
    </template>
  </n-button>
</template>

<script lang="ts" setup>
import { SunnyOutline, MoonOutline } from '@vicons/ionicons5'
import { useThemeStore } from '@/store/theme';
const store = useThemeStore()
const switchTheme = () => {
  store.theme =   store.theme === 'dark' ? 'light' : 'dark'
}
</script>

注: 有效代码就3. 中的两段,加上2. 中的windicss配置。