我们经常看到一些网站都有主题切换,例如vue官方文档。那他是怎么实现的呢?
检查元素,发现点击切换时,html
元素会动态的添加和移除一个class:dark
,然后页面主题色就变了。仔细想想,这要是放在以前,可能要写两套样式,就像这样:
body {
background-color: '#fff';
}
body.dark {
background-color: '#000';
}
这写起来得多麻烦啊,而且难以维护。
好在我们有CSS变量,早在2017年,微软宣布Edge浏览器将支持CSS变量,现在几乎所有的浏览器都已经支持了这个功能。(IE:啊这?)
css变量也是变量,就像js一样,先声明,再读取。
body {
--text-color: red;
}
.box {
color: var(--text-color);
}
已经出来很多年了,今天就不详细介绍了,有兴趣的推荐阅读 阮一峰老师的《CSS 变量教程》
今天就用vue3项目来写一个基于css变量实现的主题切换demo。
创建一个vue3项目:
npm create vue@latest
创建一个theme.css
文件。
/**
*默认主题
*/
:root {
--bg: #fff;
--text-color: #000;
}
/**
*添加属性,用来控制暗黑模式时的样式
*/
html[data-theme="dark"] {
--bg: #000;
--text-color: #fff;
}
或者像vue文档中一样使用class,如下所示:
:root {
--bg: #fff;
--text-color: #000;
}
html.dark {
--bg: #000;
--text-color: #fff;
}
但是如果某个页面内无意中野使用到同名dark这个class,可能会造成影响,我这里还是用属性。
在main.js
中引入一下theme.css
。
import './assets/theme.css'
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')
在App.vue style
中调用一下变量,并动态改变data-theme
的值:
<template>
<main>
<p>主题切换demo</p>
<button @click="change">切换</button>
</main>
</template>
<script>
let theme = 'light'
const change = () => {
theme = theme === 'light' ? 'dark' : 'light'
document.documentElement.setAttribute('data-theme', theme)
}
</script>
<style scoped>
main {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
background-color: var(--bg);
color: var(--text-color);
}
p {
margin: 20px 0;
}
</style>
看看效果:
功能基本上已经实现了,再来把这个切换操作封装成一个组件,并全局实时共享主题数据。
创建一个useTheme.js
,用来执行设置属性的操作:
import { ref, watchEffect } from 'vue'
// 默认用亮色
const theme = ref('light')
// 每次改变都设置一下
watchEffect(() => {
document.documentElement.setAttribute('data-theme', theme.value)
})
export default function useTheme() {
return {
theme
}
}
创建一个switch-theme.vue
组件,仅仅用来改变theme
的值:
<template>
<el-switch
v-model="theme"
:active-action-icon="Moon"
:inactive-action-icon="Sunny"
active-color="#2f2f2f"
active-value="dark"
inactive-value="light"
@change="changeDark"
></el-switch>
</template>
<script setup>
import { Sunny, Moon } from '@element-plus/icons-vue'
import useTheme from '../hooks/useTheme'
const { theme } = useTheme()
const changeDark = (data) => {
theme.value === data
}
</script>
改一下App.vue
文件,引入并使用ThemeSwitch
组件和useTheme
Hook:
<template>
<main>
<p>主题切换demo</p>
<ThemeSwitch></ThemeSwitch>
<p>当前主题:{{theme}}</p>
</main>
</template>
<script setup>
import ThemeSwitch from './components/theme-switch.vue'
import useTheme from './hooks/useTheme'
const { theme } = useTheme()
</script>
<style scoped>
main {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
background-color: var(--bg);
color: var(--text-color);
}
p {
margin: 20px 0;
}
</style>
再看看效果:
现在由一个专门的组件用来控制切换主题,并且不同组件内也都能共享theme
变量了。
最后再优化一下,目前默认是亮色,切换到暗色以后再刷新页面,又会回到亮色,可以把theme
变量存到localstorage
。
修改一下useTheme.js
:
import { ref, watchEffect } from 'vue'
// 从取缓存中取值,给个默认值。
const theme = ref(localStorage.getItem('theme') || 'light')
// 每次改变都设置一下属性,并存到缓存中。
watchEffect(() => {
document.documentElement.setAttribute('data-theme', theme.value)
localStorage.setItem('theme', theme.value)
})
export default function useTheme() {
return {
theme
}
}
全部代码见Github