前端实现主题切换,代码量最小的解决方法!
实现功能
之前有一个项目的UI界面进行了更新,产品提了一个需求:现在的项目的ui有部分用户觉得不好看,但是有另外一部分用户觉得以前的版本好看,是否可以实现一个功能做实现主题切换。因为这个项目代码量比较多,参与的人也很多,每个人代码风格也不一样,当时就给产品说做不了,这两天空了之后研究了一下,找了很多方案,整理了一下个人认为最简单的方案,点击切换主题不仅能更改自定义的样式,还可以修改ellementplus的组件样式,以下是完成的效果
项目简述
gitee地址:https://gitee.com/lyh509/white-v3-template
构建工具:vite
UI库:ElementPlus
代码规范工具:Eslint + Prettier + Husky
预编译器:scss
css库:tailwindCss
路由:vue-router
状态管理:Pinia
当然如果你的项目是使用vue2或者其他框架,也没有影响,大体的思路是一样的
操作步骤
1.首先按照下图创建styles文件夹及文件夹下的index.scss和theme.css
index.scss代码如下:
.bg-color {
background: var(--bg-color);
}
.text-clor {
color: var(--text-color);
}
theme.css代码如下:
:root[theme=‘xxxx’](:root表示根节点选择器,:root[theme=‘xxxx’]表示在根节点的theme属性为xxxx时样式为以下代码)
主题切换原理就是更改根节点的theme属性,来切换样式
/* 更改element的组件样式 */
:root {
--el-menu-text-color: var(--text-color);
--el-menu-bg-color: var(--bg-color);
--el-menu-border-color: var(--bg-color);
}
/* 主题一 */
:root[theme='theme_1'] {
--bg-color: #0d1117;
--text-color: #3498db;
}
/* 主题二 */
:root[theme='theme_2'] {
--bg-color: #b74141;
--text-color: #fff;
}
/* 主题三 */
:root[theme='theme_3'] {
--bg-color: #f1c40f;
--text-color: #c0392b;
}
如果想要修改element组件样式,找到想要修改颜色的element组件,打开控制台审查元素,找到要修改的颜色属性,将var()括号中的样式变量名称复制到theme.css中如上图第一个模块,将颜色更改为我们主题颜色变量名称(我们这里是以修改el-menu背景颜色为例)
2.然后在main.ts中引入
//main.ts
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
import '@/styles/theme.css'
import '@/styles/index.scss'
app.mount('#app')
3.设置class到需要应用到的组件
图片左侧的TopBar是页面的头部组件,我们在index.scss中定义了bg-color和text-color两个类名,接下来我们将这两个类名加到topBar组件的对应位置
代码截图:
4.实现主题切换
普通方法
在要切换组件的按钮或者下拉框组件上绑定changeTheme方法(我这里是用的下拉框来触发主题切换事件),在changeTheme方法里使用document.documentElement获取到页面dom的根节点,并且在该节点上设置一个名为theme的属性,当根节点属性值发生变化时,页面的颜色参数值也会改变(这个地方可以看一看上面的theme.css代码)
TopBar.vue代码:
<template>
<div class="w-full h-10 flex justify-between items-center bg-color">
<span class="text-clor font-bold w-full h-full">Vue3 框架模板</span>
<section class="flex items-center justify-end h-full">
<el-button @click="logout">注销</el-button>
<el-select
v-model="value"
placeholder="Select"
style="width: 240px"
@change="changeTheme"
>
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value"
:disabled="item.disabled"
/>
</el-select>
</section>
</div>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue'
import useCounter from '@/store/user'
import { defineProps } from 'vue'
export default defineComponent({
setup() {
const user = useCounter()
const value = ref(user.theme)
defineProps(['userInfo'])
const options = [
{
label: '主题1',
value: 'theme_1',
disabled: false,
},
{
label: '主题2',
value: 'theme_2',
disabled: false,
},
{
label: '主题3',
value: 'theme_3',
disabled: false,
},
]
/************************核心代码****************************/
const changeTheme = (val) => {
document.documentElement.setAttribute('theme', val)
}
//因为页面首次进入或者dom重新渲染时,dom根节点上没有设置theme,所以在这里先调用一次changeTheme方法,设置初始主题
changeTheme(options[0].value)
/****************************************************/
const logout = () => {
user.setIsLogin(false)
}
return { user, value, options, logout }
},
})
</script>
<style lang="scss" scoped></style>
小优化
上面那种方法可以简单的实现主题切换,但是如果我们刷新浏览器后,系统的主题又会被重置,而在现实情况下,当用户切换主题颜色后,刷新页面应该不会重置主题,所以我这里是把更改dom根节点theme属性值的操作放到了pinia中的actions操作,然后在下拉框事件中调用actions中的serTheme方法,并且对状态管理器做了持久化操作