一、原理
换肤能够实现的终极密码是——CSS变量,可以为每个主题设定一组CSS变量,包含这个主题的所有颜色、字体等信息,当需要切换主题时,只需要更改使用的CSS变量组即可。
- 声明变量。在 :root 伪类中声明 CSS 变量,这样就能在全局范围内使用变量:
:root {
--main-color: #06c;
}
- 使用变量。在你的 CSS 中,使用
var()
函数来使用 CSS 变量:
.header {
background-color: var(--main-color);
}
二、demo实现
下面我们用Vue3+Element-plus为例,来实现一波高亮模式+暗黑模式两个主题色,可参考element-plus暗黑模式介绍。
2.1 引入主题色样式
在src/styles下面新建theme.scss,把默认暗黑主题色引入进来,并可以在其里面覆盖原有变量或新增一些变量
// theme.scss
/** element内置暗黑主题 */
@use 'element-plus/theme-chalk/src/dark/css-vars.scss' as *;
// 可以进行一些样式的覆盖
html {
--v-bg-color: #cfcccc; // 新增
}
html.dark {
--v-bg-color: #141414; // 新增
--el-color-primary: #409eff; // 覆盖
}
在main.ts中引入默认主题色和暗黑模式主题色
// main.ts 文件
import { createApp } from 'vue';
import ElementPlus from 'element-plus';
// element默认主题
import 'element-plus/dist/index.css'
import './style.css';
// 公共样式,包含自定义暗黑模式,element重置样式
import './styles/index.scss';
import App from './App.vue';
const app = createApp(App);
app.use(ElementPlus);
app.mount('#app')
此时在浏览器控制台就可以看到很多变量
2.2 主题色切换能力
主题切换能力其核心关注点为:
- 利用provide注入当前主题及修改主题的方法,然后在组件中通过inject获取主题及主题修改方法;
- 利用localStorage持久化存储主题;
- 改变html的class属性,进而决定使用哪一套主题;
<script setup lang="ts">
import {provide, ref, onMounted} from 'vue';
import SwitchDark from './components/SwitchDark.vue';
// 改变属性,确定使用哪一套样式
const addThemeAttribute = (theme: string) => {
const html = document.documentElement;
html.setAttribute('class', theme);
}
const theme = ref(localStorage.getItem('myTheme') || 'light');
onMounted(() => {
addThemeAttribute(theme.value);
});
const setTheme = (newTheme: string) => {
// 改变主题
theme.value = newTheme;
addThemeAttribute(newTheme);
localStorage.setItem('myTheme', newTheme);
};
provide('theme', {
theme,
setTheme
});
</script>
<template>
<SwitchDark />
<div class="bg">
我是内容
</div>
</template>
<style scoped>
.bg {
background-color: var(--v-bg-color);
width: 200px;
height: 200px;
}
</style>
2.3 切换按钮
切换主题色肯定需要一个按钮,下面利用el-switch实现了一个简单的切换按钮,并利用setTheme来切换对应的主题。
<template>
<el-switch
v-model="isDark"
inline-prompt
@change="changeTheme"
:active-icnotallow="Sunny"
:inactive-icnotallow="Moon"
size="large"
/>
</template>
<script setup lang="ts">
import {inject, Ref, computed} from 'vue';
import {Sunny, Moon} from '@element-plus/icons-vue';
const {theme, setTheme} = inject<{theme: Ref<string>, setTheme: (newTheme: string) => void}>('theme') || {};
const isDark = computed(() => theme?.value === 'dark');
const changeTheme = () => {
console.log(theme?.value)
if (theme?.value === 'light') {
setTheme?.('dark');
} else {
setTheme?.('light');
}
};
</script>
<style scoped>
</style>