实时动态修改css :root 中的变量实现换肤、主题色的功能

前端中有用到需要设置主题颜色,根据用户喜欢实现换肤。今天分享一下使用css中 :root 变量来实现换肤、主题色的功能

先看图,再看代码
在这里插入图片描述
在这里插入图片描述

先准备工具方法,用于转换颜色值的格式

1、将一个十六进制颜色值转换成RGB颜色值

/**
 * 将一个十六进制颜色值转换成RGB颜色值
 * @param str 一个十六进制颜色值
 * @returns
 */
export function hexToRgb(str: string) {
  let hexs: any = ''
  const reg = /^#?[0-9A-Fa-f]{6}$/
  if (!reg.test(str)) {
    return ElMessage.warning('输入错误的hex')
  }
  str = str.replace('#', '')
  hexs = str.match(/../g)
  for (let i = 0; i < 3; i++) {
    hexs[i] = parseInt(hexs[i], 16)
  }
  return hexs
}

2、将RGB颜色值转换成一个十六进制颜色值

/**
 * 将RGB颜色值转换成一个十六进制颜色值
 * @param r 表示红颜色通道的十进制数值
 * @param g 表示绿颜色通道的十进制数值
 * @param b 表示蓝颜色通道的十进制数值
 * @returns
 */
export function rgbToHex(r: any, g: any, b: any) {
  const reg = /^\d{1,3}$/
  if (!reg.test(r) || !reg.test(g) || !reg.test(b)) {
    return ElMessage.warning('输入错误的rgb颜色值')
  }
  const hexs = [r.toString(16), g.toString(16), b.toString(16)]
  for (let i = 0; i < 3; i++) {
    if (hexs[i].length == 1) {
      hexs[i] = `0${hexs[i]}`
    }
  }
  return `#${hexs.join('')}`
}

3、根据给定的十六进制颜色值和暗度级别来生成一个更暗的颜色

/**
 * 根据给定的十六进制颜色值和暗度级别来生成一个更暗的颜色
 * @param color 表示颜色值
 * @param level 表示亮度级别
 * @returns
 */
export function getDarkColor(color: string, level: number) {
  const reg = /^\#?[0-9A-Fa-f]{6}$/
  if (!reg.test(color)) {
    return ElMessage.warning('输入错误的hex颜色值')
  }
  const rgb = hexToRgb(color)
  for (let i = 0; i < 3; i++) {
    rgb[i] = Math.round(20.5 * level + rgb[i] * (1 - level))
  }
  return rgbToHex(rgb[0], rgb[1], rgb[2])
}

4、根据给定的十六进制颜色值和亮度级别来生成一个更亮的颜色

/**
 * 根据给定的十六进制颜色值和亮度级别来生成一个更亮的颜色
 * @param color 表示颜色值
 * @param level 表示亮度级别
 * @returns
 */
export function getLightColor(color: string, level: number) {
  const reg = /^#?[0-9A-Fa-f]{6}$/
  if (!reg.test(color)) {
    return ElMessage.warning('输入错误的hex颜色值')
  }
  const rgb = hexToRgb(color)
  for (let i = 0; i < 3; i++) {
    rgb[i] = Math.round(255 * level + rgb[i] * (1 - level))
  }
  return rgbToHex(rgb[0], rgb[1], rgb[2])
}

5、定义颜色值的全局状态

import { defineStore } from 'pinia'
import { themeColor, otherColor } from '@/config/themeColor'
import type { ISettingsState } from '../types/setting'
export const useSettingsStore = defineStore('settings', {
  state: (): ISettingsState => ({
    collapse: false,
    refresh: false, // 刷新页面
    isDark: false,
    otherColor: {
      menubgcolor: otherColor.DEFAULT_MENUGACKGROUND,
    },
    themeColor: {
      primary: themeColor.DEFAULT_PRIMARY,
      success: themeColor.DEFAULT_SUCCESS,
      warning: themeColor.DEFAULT_WARNING,
      danger: themeColor.DEFAULT_DANGER,
      info: themeColor.DEFAULT_INFO,
    },
  }),

  actions: {
    changeCollapse() {
      this.collapse = !this.collapse
    },
    setRefresh() {
      this.refresh = !this.refresh
    },
    setIsDark() {
      this.isDark = !this.isDark
    },
    setOtherColor(colorType: string, val: string){
      this.otherColor[colorType] = val
    },
    setThemeConfig(colorType: string, val: string) {
      this.themeColor[colorType] = val
    },
  },
  persist: true,
})

6、具体修改颜色值后调用方法

import { computed } from 'vue'
import i18n from '@/i18n/index'
import { ElMessage } from 'element-plus'
import { getDarkColor, getLightColor } from '@/utils/colorConversion'
import { useSettingsStore } from '@/store/modules/setting'

export const useTheme = () => {
  const settingsStore = useSettingsStore()
  const themeColor = computed(() => settingsStore.themeColor)
  const isDark = computed(() => settingsStore.isDark)

  // 切换暗黑模式
  const switchDark = () => {
    const body = document.documentElement as HTMLElement
    if (isDark.value) {
      body.setAttribute('class', 'dark')
    } else {
      body.setAttribute('class', '')
    }
    changeThemeColor(themeColor.value.primary, 'primary', true)
  }

  // 修改主题颜色
  const changeThemeColor = (
    val: string,
    colorType = 'primary',
    isInit = false
  ) => {
    if (!isInit) {
      ElMessage.success(`${i18n.global.t('settings.colorSetTip')} ${val}`)
    }
    settingsStore.setThemeConfig(colorType, val)
    document.documentElement.style.setProperty(
      `--el-color-${colorType}`,
      themeColor.value[colorType]
    )
    // 颜色加深或变浅
    for (let i = 1; i <= 9; i++) {
      document.documentElement.style.setProperty(
        `--el-color-${colorType}-light-${i}`,
        isDark.value
          ? `${getDarkColor(themeColor.value[colorType], i / 10)}`
          : `${getLightColor(themeColor.value[colorType], i / 10)}`
      )
    }
  }
  // 修改其他颜色值
  const changeOtherColor = (colorType: string, val: string) => {
    settingsStore.setOtherColor(colorType, val)
    document.documentElement.style.setProperty(`--${colorType}`, val)
  }

  // 初始化主题
  const initTheme = () => {
    const colorArr = Object.keys(themeColor.value)
    for (let index = 0; index < colorArr.length; index++) {
      const color = colorArr[index]
      changeThemeColor(themeColor.value[color], color, true)
    }
    switchDark()
  }
  return {
    initTheme,
    switchDark,
    changeThemeColor,
    changeOtherColor
  }
}

7、组件页面的代码

<template>
  <el-drawer title="主题设置" v-model="drawerVisible" size="300px">
    <el-divider class="divider" content-position="center">全局主题</el-divider>
    <div class="theme-item">
      <span>主题颜色</span>
      <el-color-picker
        v-model="ThemeColorObj.primary"
        :predefine="colorList"
        @change="changeThemeColor(ThemeColorObj.primary, 'primary')"
      />
    </div>
    <div class="theme-item">
      <span>成功颜色</span>
      <el-color-picker
        v-model="ThemeColorObj.success"
        @change="changeThemeColor(ThemeColorObj.success, 'success')"
      />
    </div>
    <div class="theme-item">
      <span>警告颜色</span>
      <el-color-picker
        v-model="ThemeColorObj.warning"
        @change="changeThemeColor(ThemeColorObj.warning, 'warning')"
      />
    </div>
    <div class="theme-item">
      <span>危险颜色</span>
      <el-color-picker
        v-model="ThemeColorObj.danger"
        @change="changeThemeColor(ThemeColorObj.danger, 'danger')"
      />
    </div>
    <div class="theme-item">
      <span>信息颜色</span>
      <el-color-picker
        v-model="ThemeColorObj.info"
        @change="changeThemeColor(ThemeColorObj.info, 'info')"
      />
    </div>
    <div class="theme-item">
      <span>暗黑模式</span>
      <el-switch
        v-model="isDarkValue"
        @change="onAddDarkChange"
        inline-prompt
        :active-icon="Sunny"
        :inactive-icon="Moon"
      />
    </div>
    <div class="theme-item">
      <span>菜单背景色</span>
      <el-color-picker
        v-model="otherColorObj.menubgcolor"
        @change="changeOtherColor('menubgcolor', otherColorObj.menubgcolor)"
      />
    </div>
  </el-drawer>
</template>

<script setup lang="ts">
import mittBus from '@/utils/mittBus'
import { themeColor } from '@/config/themeColor'
import { useSettingsStore } from '@/store/modules/setting'
import { useTheme } from '@/hooks/useTheme'
import { Sunny, Moon } from '@element-plus/icons-vue'
const { changeThemeColor, switchDark, changeOtherColor } = useTheme()
const settingsStore = useSettingsStore()
const ThemeColorObj = computed(() => settingsStore.themeColor)
const otherColorObj = computed(() => settingsStore.otherColor)
const isDarkValue = computed(() => settingsStore.isDark)
const onAddDarkChange = () => {
  settingsStore.setIsDark()
  switchDark()
}
// 预定义主题颜色
const colorList = [
  themeColor.DEFAULT_PRIMARY,
  '#DAA96E',
  '#0C819F',
  '#722ed1',
  '#27ae60',
  '#ff5c93',
  '#e74c3c',
  '#fd726d',
  '#f39c12',
  '#9b59b6',
]
// 打开主题设置
const drawerVisible = ref(false)
mittBus.on('openThemeDrawer', () => {
  drawerVisible.value = true
})
</script>

<style scoped lang="scss">
.theme-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin: 14px 0;
}
</style>

到此就完成了动态修改:root变量实现换肤了,具体的应用场景中我们应该是在页面上操作,选中需要的肤色然后修改:root定义的变量值,考虑到浏览器刷新会重置原始状态的值,这里要结合本地存储方式记住选中的肤色。如有问题欢迎大家指出!

  • 17
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值