暗黑模式在过去几年中最受欢迎,各大操作系统也全部支持了暗黑模式,并在浏览器中提供了查询是否开启暗黑模式的接口。接下来以vue3的element-plus为例,通过切换全局css变量的方式,实现主题换肤与暗黑模式设置。

一、主流换肤实现思路

1. 修改全局CSS变量

<style> :root{ --background_color:red } </style>

<style scoped> .test{ background: var(--background_color); } </style>

const changeTheme = () => { document.documentElement.style.setProperty("--background_color","green"); }

2. 切换主题CSS文件

# assets/css/theme_green.css .bg { background: green; } # assets/css/theme_red.css .bg { background: red; }

# src/App.vue import {onMounted} from "vue"; onMounted(() => { let link = document.createElement('link'); link.type = 'text/css'; link.id = "theme"; link.rel = 'stylesheet'; link.href = 'assets/css/theme_red.css'; document.getElementsByTagName("head")[0].appendChild(link); },

const changeTheme = () => { document.getElementById('theme').href = 'assets/css/theme_green.css' }

3. 使用css预处理器(例如 scss,通过mixin混入器切换)

$themes: ( light: ( //字体 font_color1: #414141, font_color2: white, //背景 background_color1: #fff, background_color2: #f0f2f5, ), dark: ( //字体 font_color1: #a7a7a7, font_color2: white, //背景 background_color1: #1b2531, background_color2: #283142, ) );

@import "./_themes.scss"; //遍历主题map @mixin themeify { @each $theme-name, $theme-map in $themes { //!global 把局部变量强升为全局变量 $theme-map: $theme-map !global; //判断html的data-theme的属性值 #{}是sass的插值表达式 //& sass嵌套里的父容器标识 @content是混合器插槽,像vue的slot [data-theme="#{$theme-name}"] & { @content; } } } //声明一个根据Key获取颜色的function @function themed($key) { @return map-get($theme-map, $key); } //获取背景颜色 @mixin background_color($color) { @include themeify { background-color: themed($color)!important; } } //获取字体颜色 @mixin font_color($color) { @include themeify { color: themed($color)!important; } }

<style lang="scss" scoped> @import "@/style/_handle.scss"; .test { @include font_color("font_color1"); @include background_color("background_color1"); } </style>

const theme = (type) => { window.document.documentElement.setAttribute( "data-theme", type ); }

二、项目案例

1. 思路分析

参考element-plus文档https://element-plus.gitee.io/zh-CN/guide/theming.html#更换主题色,element-plus提供了SCSS变量和CSS变量两种方式实现主题换肤。相比较而言,使用CSS变量实现更加便捷,代码也更易编写。

要想实现暗黑模式和明亮模式切换,首先使用vuex定义全局主题色已经是否开启暗黑模式变量并管理。然后创建一个颜色处理模块以及暗黑模式、主题色处理函数文件。最后根据需求,直接替换颜色即可。

2. 项目目录结构

├── store // vuex文件夹 | ├── index.js // vuex入口文件 | ├── mutations.js // 更改state中状态的逻辑 | └── state.js // 存放状态 ├── utils | ├── color.js // 定义主题色以及暗黑模式、明亮模式颜色变量 | ├── dark.js // 暗黑模式切换处理模块 | └── theme.js // 主题色切换模块 ├── components | └── NavMenu.vue // 自定义组件,用于设置是否切换暗黑模式、主题色等操作 ├── views | └── Home.vue // 项目Home页

3. vuex配置

const state = { // 是否开启暗黑模式 dark: false, // 默认主题色 theme:'#409eff', } export default state

const mutations = { // 是否开启暗黑模式 setDark(state, value) { state.dark = value }, // 设置主题色 setTheme(state, value) { state.theme = value }, } export default mutations

import {createStore} from 'vuex' import mutations from '@/store/mutations' import state from "@/store/state"; import createVuexAlong from 'vuex-along' export default createStore({ state, mutations, plugins: [ createVuexAlong({ local: { list: [ ], }, session: { list: ["dark", "theme"], } }) ] })

3. 定义颜色处理模块

// 颜色管理 import {ref} from "vue"; function color() { // 主题颜色变量组 const themeList = ref([ {name: '拂晓蓝(默认)', value: '#409eff'}, {name: '薄暮红', value: '#e74c3c'}, {name: '火山橘', value: '#e67e22'}, {name: '日暮黄', value: '#f1c40f'}, {name: '极光绿', value: '#16a085'}, {name: '酱紫', value: '#9b59b6'}, ]) // 明亮模式全局颜色 const lightList = ref([ {name: '--el-background-color-base', value: '#f5f7fa'}, {name: '--el-color-white', value: '#ffffff'}, {name: '--el-text-color-primary', value: '#303133'}, {name: '--el-text-color-regular', value: '#606266'}, {name: '--el-color-primary-light-9', value: '#ecf5ff'}, {name: '--el-border-color-base', value: '#dcdfe6'}, {name: '--el-border-color-light', value: '#e4e7ed'}, {name: '--el-border-color-extra-light', value: '#f2f6fc'}, {name: '--el-border-color-lighter', value: '#ebeef5'}, ]) // 暗黑模式全局颜色 const darkList = ref([ {name: '--el-background-color-base', value: '#000000'}, {name: '--el-color-white', value: '#141414'}, {name: '--el-text-color-primary', value: '#ffffff'}, {name: '--el-text-color-regular', value: '#d0d0d0'}, {name: '--el-color-primary-light-9', value: '#1f1f1f'}, {name: '--el-border-color-base', value: '#434343'}, {name: '--el-border-color-light', value: '#434343'}, {name: '--el-border-color-extra-light', value: '#5c5c5c'}, {name: '--el-border-color-lighter', value: '#434343'}, ]) return { darkList, themeList } } export default color

import {computed, onMounted, ref} from "vue"; import store from "@/store/index"; import color from "@/utils/color" import {useCssVar} from "@vueuse/core"; function dark() { let {darkList, lightList} = color() // 当前是否开启暗黑模式 const isDark = computed(() => store.state.dark) // 切换暗黑模式 const setDark = (value) => { store.commit('setDark', value) console.log("执行切换开启暗黑模式事件:", isDark.value) const el = ref(null) if (isDark.value) { console.log("要切换为暗黑模式") darkList.value.forEach((item, index) => { index = useCssVar(item.name, el) index.value = item.value }) } else { console.log("要切换为明亮模式") lightList.value.forEach((item, index) => { index = useCssVar(item.name, el) index.value = item.value }) } } onMounted(() => { // console.log("是否开启暗黑模式:",isDark) setDark(isDark.value) }) return { setDark, } } export default dark

import {computed, onMounted, ref} from "vue"; import store from "@/store/index"; import {useCssVar} from "@vueuse/core"; function theme() { // 当前默认主题色 const themeValue = computed(() => store.state.theme) // 切换主题色 const setTheme = (value) => { store.commit('setTheme', value) console.log("执行切换主题色事件:", themeValue.value) const el = ref(null) const primary_color = useCssVar('--el-color-primary', el) primary_color.value = themeValue.value } onMounted(() => { setTheme(themeValue.value) }) return { setTheme, } } export default theme

4. element-plus组件主题配置

通过浏览器审查元素可知,element plus组件都使用了css全局变量色,切换暗黑主题时,只需要更改全局变量的值即可,切换主题时,更换el-color-primary的值即可。

elementui主题切换(element-plus项目主题换肤与暗黑模式)(1)

5. 自定义组件主题配置

为了保持全局样式风格统一,自定义组件设置样式时,使用element-plus定义的全局变量即可。

.test { cursor: pointer; background-color: var(--el-color-primary); transition: all 0.5s; }

6. 组件调用切换主题和深色模式

<template> <transition enter-active-class="animate__animated animate__fadeInDown" leave-active-class="animate__animated animate__fadeOutUp"> <header> <el-drawer title="系统设置" v-model="drawer" :direction="'rtl'" :size="'25%'" :before-close="handleClose" destroy-on-close > <span> <el-divider></el-divider> <div class="display"> <h4>显示模式</h4> <span> <img :class="isDark===true?'':'img-active'" src="~@/assets/images/light.png" alt=""> <img :class="isDark===false?'':'img-active'" src="~@/assets/images/dark.png" alt=""> </span> <el-switch style="display: block" v-model="isDark" active-color="#303133" inactive-color="#f5f7fa" active-text="深色模式" inactive-text="浅色模式" @change="setDarkMode" /> </div> <el-divider></el-divider> <div class="color"> <h4>主题色</h4> <div> <el-tooltip v-for="(item,index) in themeList" :key="index" effect="dark" :content="item.name" placement="top"> <span :style="{backgroundColor:item.value}" :class="(colorValue===item.value?'color-active ':'')" @click="colorChoose(item.value)"></span> </el-tooltip> </div> </div> <el-divider></el-divider> </el-drawer> </header> </transition> <div class="placeholder"></div> </template> <script setup> import {ElMessageBox, ElMessage} from 'element-plus' import {computed, onMounted, reactive, ref} from "vue"; import store from "@/store/index"; import dark from "@/utils/dark"; import color from "@/utils/color" import theme from "@/utils/theme" let {setDark} = dark() let {setTheme} = theme() let {themeList} = color() //设置-菜单默认关闭 let drawer = ref(false); //设置-菜单关闭事件 const handleClose = () => { drawer.value = false }; // 设置-显示模式默认值 const isDark = ref(false) // 设置-切换是否设置暗黑模式 const setDarkMode = () => { console.log("菜单栏执行切换事件", isDark.value) setDark(isDark.value) } // 设置-默认主题色 const colorValue = ref('') // 设置-切换主题色事件 const colorChoose = (value) => { colorValue.value = value console.log(colorValue.value) setTheme(colorValue.value) } </script> <style scoped lang="scss"> </style>

7. 跟随系统设置暗黑主题

使用 matchMedia 方法可以直接获取浏览器当前是否使用深色模式

<template> <div class="router-view"> <router-view v-slot="{ Component }"> <component :is="Component"/> </router-view> </div> </template> <script setup> import {onMounted} from "vue"; import dark from "@/utils/dark"; let {setDark} = dark() const locale = zhCn onMounted(() => { const is_dark = window.matchMedia('(prefers-color-scheme: dark)').matches setDark(is_dark) }) </script> <style lang="scss"> .router-view { color: var(--el-text-color-primary); background-color: var(--el-background-color-base); transition: background 1s, color 0.6s; width: 100%; height: max-content; min-height: 100vh; position: absolute; top: 0; bottom: 0; margin: 0 auto; -webkit-overflow-scrolling: touch; animation-timing-function: linear; } </style>

三、效果演示

1. 设置暗黑模式与主题色菜单

elementui主题切换(element-plus项目主题换肤与暗黑模式)(2)

2. 开启暗黑模式

elementui主题切换(element-plus项目主题换肤与暗黑模式)(3)

3. 切换主题色(深色绿)

elementui主题切换(element-plus项目主题换肤与暗黑模式)(4)

4. 切换主题色(浅色紫)

elementui主题切换(element-plus项目主题换肤与暗黑模式)(5)

更多运维开发相关文章,欢迎访问崔亮的博客 https://www.cuiliangblog.cn

,