基于elment-plus的暗黑模式及主题切换

1,004 阅读2分钟

暗黑模式及主题切换:

项目中使用的组件库为 element-plus, 同时使用tailwind语法构建样式

因此直接使用elment-plus的暗黑以及主题切换

一、element-plus主题切换

原理:通过阅读element源码 目录:/theme-chalk/src/common/var.scss

// elment主题色 --el-color-primary
$color-primary: map.get($colors, 'primary', 'base') !default;

// 混入不同明亮等级样式
@mixin set-color-mix-level(
  $type,
  $number,
  $mode: 'light', // 明亮模式
  $mix-color: $color-white
) {
  $colors: map.deep-merge(
    (
      $type: (
        '#{$mode}-#{$number}':
          mix(
            $mix-color,
            map.get($colors, $type, 'base'),
            math.percentage(math.div($number, 10))
          ),
      ),
    ),
    $colors
  ) !global;
}

我们可以知道与主题色相关的被分为十个等级,并不断变淡。最终得出与主题色相关的变量如下

// $colors.primary.light-i
// --el-color-primary-light-i
// 10% 53a8ff
// 20% 66b1ff
// 30% 79bbff
// 40% 8cc5ff
// 50% a0cfff
// 60% b3d8ff
// 70% c6e2ff
// 80% d9ecff
// 90% ecf5ff

因此我们可以通过js设置这几个css变量达到主题切换的效果

const el = document.documentElement
el.style.setProperty('--el-color-primary', 'red')

从源码解析,element在暗黑模式只用了一种css变量 --el-color-primary-dark-2

// --el-color-primary-dark-2
@each $type in $types {
  @include set-color-mix-level($type, 2, 'dark', $color-black);
}

接下来创建调节颜色的工具方法

// tools.ts
/**
 * 加深颜色值
 * @param color 颜色值字符串
 * @param level 加深的程度,限0-1之间
 * @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]);
}

/**
 * 变浅颜色值
 * @param color 颜色值字符串
 * @param level 加深的程度,限0-1之间
 * @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]);
}

创建好更改的方法,可以动态的切换主题颜色了

  // useTheme.ts
  // 修改主题颜色
  const changePrimary = (val: string) => {
    if (!val) {
      val = DEFAULT_PRIMARY;
      ElMessage.success(`主题色已重置为 ${DEFAULT_PRIMARY}`);
    }
    appStore.setThemeConfig({ ...themeConfig.value, primary: val });

    document.documentElement.style.setProperty("--el-color-primary", themeConfig.value.primary);
    document.documentElement.style.setProperty("--el-color-primary-dark-2", themeConfig.value.isDark ? `${getLightColor(themeConfig.value.primary, 0.2)}` : `${getDarkColor(themeConfig.value.primary, 0.3)}`);
    document.documentElement.style.setProperty("--primary", themeConfig.value.primary);

    // 颜色加深或变浅
    for (let i = 1; i <= 9; i++) {
      document.documentElement.style.setProperty(`--el-color-primary-light-${i}`, themeConfig.value.isDark ? `${getDarkColor(themeConfig.value.primary, i / 10)}` : `${getLightColor(themeConfig.value.primary, i / 10)}`);
      document.documentElement.style.setProperty(`--primary-light-${i}`, themeConfig.value.isDark ? `${getDarkColor(themeConfig.value.primary, i / 10)}` : `${getLightColor(themeConfig.value.primary, i / 10)}`);
    }
  };

添加暗黑模式

引入暗黑模式的样式

// main.ts
import "element-plus/theme-chalk/dark/css-vars.css";

创建切换暗黑模式的方法,就是给body加上类名

  // isDark字段控制明暗状态,swichDark根据isDark执行切换
  // 切换暗黑模式
  const switchDark = () => {
    const body = document.documentElement as HTMLElement;
    const html = document.querySelector("html") as HTMLElement;
    if (themeConfig.value.isDark) {
      setDarkTheme();
      body.setAttribute("class", "dark");
    } else {
      setLightTheme();
      body.setAttribute("class", "");
    }
    changePrimary(themeConfig.value.primary);
  };

对于背景或者部分组件样式,可以创建scss文件引入覆盖来实现自定义

/* 自定义 element 暗黑模式样式 */
html.dark {
    // layout
    .el-container {
        //左边栏
        .el-aside {
            background-color: var(--boxbg) !important;
        }
        .el-main {
            background-color: var(--main) !important;
        }

    }

    // menu 菜单的字体图标颜色变化
    .el-menu,
    .el-sub-menu,
    .el-menu-item,
    .el-sub-menu__title {
        background-color: var(--boxbg) !important;
        &:not(.is-active) {
            color: #bdbdc0 !important;
        }
        &.is-active {
            color: #ffffff !important;
            background-color: #000000 !important;
        }
    }
    .el-menu-item:not(.is-active):hover {
        background-color: var(--main) !important;
    }
    // 全局字体变化
    color: #A6ADBA;
}

如果你使用了daisyUI的主题CSS变量,或者tailwind中设置了css变量想根据明暗切换。可以创建一个文件用来改变CSS变量

const light = {
  "base-100": "#ffffff",
  "base-300": "#E5E6E6",
  "base-content": "#1f2937",
  "base-200": "#fbfbfb",
  "--boxbg": "#ffffff",
  "--border": "#D9D9D9",
  "--text": "#D9D9D9",
  "--main": "#fbfbfb",
};

const dark = {
  "base-100": "#2A303C",
  "base-200": "#242933",
  "base-300": "#20252E",
  "base-content": "#A6ADBB",
  //s
  "--boxbg": "#141414",
  "--border": "#4c4c4d",
  "--text": "#4c4c4d",
  "--main": "#0d0d0d",
};

export const setDarkTheme = () => {
  const arr = Object.keys(dark);
  arr.forEach((key) => {
    document.documentElement.style.setProperty(key, dark[key]);
  });
};

export const setLightTheme = () => {
  const arr = Object.keys(light);
  arr.forEach((key) => {
    document.documentElement.style.setProperty(key, light[key]);
  });
};

最终的hook文件

import { getLightColor, getDarkColor } from "./tools";
import { DEFAULT_PRIMARY } from "@/utils/globalConfig";
import { BRAND_THEME, BRAND_ADDRESS } from "@/utils/globalConfig";
import { useAppStore } from "@/vendors/store/modules/app";
import { setDarkTheme, setLightTheme } from "./setColor";
export const useTheme = () => {
  const appStore = useAppStore();
  const themeConfig = computed(() => appStore.themeConfig);

  // isDark字段控制明暗状态,swichDark根据isDark执行切换
  // 切换暗黑模式
  const switchDark = () => {
    const body = document.documentElement as HTMLElement;
    const html = document.querySelector("html") as HTMLElement;
    if (themeConfig.value.isDark) {
      setDarkTheme();
      body.setAttribute("class", "dark");
    } else {
      setLightTheme();
      body.setAttribute("class", "");
    }
    changePrimary(themeConfig.value.primary);
  };

  // 修改主题颜色
  const changePrimary = (val: string) => {
    if (!val) {
      val = DEFAULT_PRIMARY;
      ElMessage.success(`主题色已重置为 ${DEFAULT_PRIMARY}`);
    }
    appStore.setThemeConfig({ ...themeConfig.value, primary: val });

    document.documentElement.style.setProperty("--el-color-primary", themeConfig.value.primary);
    document.documentElement.style.setProperty("--el-color-primary-dark-2", themeConfig.value.isDark ? `${getLightColor(themeConfig.value.primary, 0.2)}` : `${getDarkColor(themeConfig.value.primary, 0.3)}`);
    document.documentElement.style.setProperty("--primary", themeConfig.value.primary);

    // 颜色加深或变浅
    for (let i = 1; i <= 9; i++) {
      document.documentElement.style.setProperty(`--el-color-primary-light-${i}`, themeConfig.value.isDark ? `${getDarkColor(themeConfig.value.primary, i / 10)}` : `${getLightColor(themeConfig.value.primary, i / 10)}`);
      document.documentElement.style.setProperty(`--primary-light-${i}`, themeConfig.value.isDark ? `${getDarkColor(themeConfig.value.primary, i / 10)}` : `${getLightColor(themeConfig.value.primary, i / 10)}`);
    }
  };

  // 初始化 theme 配置
  const initTheme = () => {
    useAppStore().themeConfig.isDark = localStorage.getItem("syncTheme") === "dark";
    switchDark();
    const themeColor = getTheme();
    changePrimary(themeColor);
  };

  // 获取主题
  const getTheme = () => {
    const currentHost = window.location.host;
    const PROJECT_NAME = BRAND_ADDRESS[currentHost] || "Hamedal";

    return BRAND_THEME[PROJECT_NAME];
  };

  return { changePrimary, initTheme, switchDark };
};