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会很烦。
进阶解决方案
- 获取系统当前方案
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'
})
- 用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>是主题切换,下一步再讲。看看效果吧:
- 手动主题切换
说直白一点,就是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配置。