element如何动态切换主题(vite+vue+ts+elementPlus)


前言

提示:动态切换主题使用的是css3的var函数现实

示例:切换--main-bg-color的值,使用<div style="--main-bg-color:red"> </div> 那么当前标签下的所有节点使用到当前变量的都会发生变化

:root {
  --main-bg-color: coral;
}
 
#div1 {
  background-color: var(--main-bg-color);
}
 
#div2 {
  background-color: var(--main-bg-color);
}

一、elementui css变量都有哪些

 通过浏览器检查,发现element ui库的变量

二、如何修改这些变量

提示: 众所周知css是从上往下执行,如果我们可以在hand标签里最后一个添加一个style标签,是不是就可以重写掉element ui的变量

1.添加在head标签里面添加style标签(在main.ts添加如下代码)

代码如下(示例):

 

代码如下: 

const style = document.createElement("style");
style.innerText = ":root{--el-color-primary:red}";
document.head.appendChild(style);

可以发现elementui的主色调已经被我们替换为红色

三、如何动态修改颜色

 前言: 

  1. 查看element.sass源码可以发现,一个primary样式,可以生成多个渐变色
    $types: primary, success, warning, danger, error, info;
    @each $type in $types {
      @for $i from 1 through 9 {
        @include set-color-mix-level($type, $i, 'light', $color-white);
      }
    }
    
    // --el-color-primary-dark-2
    @each $type in $types {
      @include set-color-mix-level($type, 2, 'dark', $color-black);
    }

不难发现 --el-color-primary-dark-2变量 是当前primary颜色跟黑色混和百分之20生成的颜色

所以设置一个primary的颜色会生成会生成1-9的和白色混的渐变色,生产一个百分之20的和黑色混的颜色

那么我们的变量就分为俩种:

  推断类型: 通过一个主色调变量,生成n个渐变色

  键值类型:一个变量对应一个值 

直接上代码

定义数据类型

interface ThemeSetting {
  /**
   *element-ui Namespace
   */
  namespace: string;
  /**
   * 数据分隔符
   */
  division: string;
  /**
   * 前缀
   */
  startDivision: string;
  /**
   * 颜色外推设置
   */
  colorInferSetting: ColorInferSetting;
}

/**
 * 颜色混和设置
 */
interface ColorInferSetting {
  /**
   * 与白色混
   */
  light: Array<number>;
  /**
   * 与黑色混
   */
  dark: Array<number>;
  /**
   * 类型
   */
  type: string;
}

/**
 * 平滑数据
 */
interface KeyValueData {
  [propName: string]: string;
}
type UpdateInferData = KeyValueData;

type UpdateKeyValueData = KeyValueData;
/**
 *平滑数据
 */
interface InferData {
  /**
   * 设置
   */
  setting?: ColorInferSetting | any;
  /**
   * 健
   */
  key: string;
  /**
   * 值
   */
  value: string;
}

export type {
  KeyValueData,
  InferData,
  ThemeSetting,
  UpdateInferData,
  UpdateKeyValueData,
};

定义配置数据

import type { ThemeSetting } from "./type";
const setting: ThemeSetting = {
  namespace: "el",
  division: "-",
  startDivision: "--",
  colorInferSetting: {
    light: [3, 5, 7, 8, 9],
    dark: [2],
    type: "color",
  },
};
export default setting;

定义默认推断数据

import type { InferData } from "./type";
const inferDatas: Array<InferData> = [];
export default inferDatas;

定义默认键值数据

import type { KeyValueData } from "./type";
const keyValueData: KeyValueData = {
  "menu-item-height": "56px",
};
export default keyValueData;

编写动态修改主题代码

import type {
  ThemeSetting,
  InferData,
  KeyValueData,
  UpdateInferData,
  UpdateKeyValueData,
} from "./type";
import tinycolor from "@ctrl/tinycolor";

declare global {
  interface ChildNode {
    innerText: string;
  }
}
class Theme {
  /**
   * 主题设置
   */
  themeSetting: ThemeSetting;
  /**
   * 键值数据
   */
  keyValue: KeyValueData;
  /**
   * 外推数据
   */
  inferDatas: Array<InferData>;
  /**
   *是否是第一次初始化
   */
  isFirstWriteStyle: boolean;
  /**
   * 混色白
   */
  colorWhite: string;
  /**
   * 混色黑
   */
  colorBlack: string;

  constructor(
    themeSetting: ThemeSetting,
    keyValue: KeyValueData,
    inferDatas: Array<InferData>
  ) {
    this.themeSetting = themeSetting;
    this.keyValue = keyValue;
    this.inferDatas = inferDatas;
    this.isFirstWriteStyle = true;
    this.colorWhite = "#ffffff";
    this.colorBlack = "#000000";
  }

  /**
   * 拼接
   * @param setting 主题设置
   * @param names   需要拼接的所有值
   * @returns       拼接后的数据
   */
  getVarName = (setting: ThemeSetting, ...names: Array<string>) => {
    return (
      setting.startDivision +
      setting.namespace +
      setting.division +
      names.join(setting.division)
    );
  };

  /**
   * 转换外推数据
   * @param setting      主题设置对象
   * @param inferData    外推数据
   * @returns
   */
  mapInferMainStyle = (setting: ThemeSetting, inferData: InferData) => {
    const key: string = this.getVarName(
      setting,
      inferData.setting
        ? inferData.setting.type
        : setting.colorInferSetting.type,
      inferData.key
    );
    return {
      [key]: inferData.value,
      ...this.mapInferDataStyle(setting, inferData),
    };
  };
  /**
   * 转换外推数据
   * @param setting    设置
   * @param inferDatas 外推数据
   */
  mapInferData = (setting: ThemeSetting, inferDatas: Array<InferData>) => {
    return inferDatas
      .map((itemData) => {
        return this.mapInferMainStyle(setting, itemData);
      })
      .reduce((pre, next) => {
        return { ...pre, ...next };
      }, {});
  };
  /**
   * 转换外推数据
   * @param setting      主题设置对象
   * @param inferData    外推数据
   * @returns
   */
  mapInferDataStyle = (setting: ThemeSetting, inferData: InferData) => {
    const inferSetting = inferData.setting
      ? inferData.setting
      : setting.colorInferSetting;
    if (inferSetting.type === "color") {
      return Object.keys(inferSetting)
        .map((key: string) => {
          if (key === "light" || key === "dark") {
            return inferSetting[key]
              .map((l) => {
                const varName = this.getVarName(
                  setting,
                  inferSetting.type,
                  inferData.key,
                  key,
                  l.toString()
                );
                return {
                  [varName]: tinycolor(inferData.value)
                    .mix(
                      key === "light" ? this.colorWhite : this.colorBlack,
                      l * 10
                    )
                    .toHexString(),
                };
              })
              .reduce((pre, next) => {
                return { ...pre, ...next };
              }, {});
          }
          return {};
        })
        .reduce((pre, next) => {
          return { ...pre, ...next };
        }, {});
    }
    return {};
  };

  /**
   *
   * @param themeSetting 主题设置
   * @param keyValueData 键值数据
   * @returns            映射后的键值数据
   */
  mapKeyValue = (themeSetting: ThemeSetting, keyValueData: KeyValueData) => {
    return Object.keys(keyValueData)
      .map((key: string) => {
        return {
          [this.updateKeyBySetting(key, themeSetting)]: keyValueData[key],
        };
      })
      .reduce((pre, next) => {
        return { ...pre, ...next };
      }, {});
  };
  /**
   * 根据配置文件修改Key
   * @param key          key
   * @param themeSetting 主题设置
   * @returns
   */
  updateKeyBySetting = (key: string, themeSetting: ThemeSetting) => {
    return key.startsWith(themeSetting.startDivision)
      ? key
      : key.startsWith(themeSetting.namespace)
      ? themeSetting.startDivision + key
      : key.startsWith(themeSetting.division)
      ? themeSetting.startDivision + themeSetting.namespace
      : themeSetting.startDivision +
        themeSetting.namespace +
        themeSetting.division +
        key;
  };
  /**
   *
   * @param setting    主题设置
   * @param keyValue   主题键值对数据
   * @param inferDatas 外推数据
   * @returns 合并后的键值对数据
   */
  tokeyValueStyle = () => {
    return {
      ...this.mapInferData(this.themeSetting, this.inferDatas),
      ...this.mapKeyValue(this.themeSetting, this.keyValue),
    };
  };

  /**
   * 将keyValue对象转换为S
   * @param keyValue
   * @returns
   */
  toString = (keyValue: KeyValueData) => {
    const inner = Object.keys(keyValue)
      .map((key: string) => {
        return key + ":" + keyValue[key] + ";";
      })
      .join("");
    return `@charset "UTF-8";:root{${inner}}`;
  };

  /**
   *
   * @param elNewStyle 新的变量样式
   */
  writeNewStyle = (elNewStyle: string) => {
    if (this.isFirstWriteStyle) {
      const style = document.createElement("style");
      style.innerText = elNewStyle;
      document.head.appendChild(style);
      this.isFirstWriteStyle = false;
    } else {
      if (document.head.lastChild) {
        document.head.lastChild.innerText = elNewStyle;
      }
    }
  };

  /**
   * 修改数据并且写入dom
   * @param updateInferData   平滑数据修改
   * @param updateKeyvalueData keyValue数据修改
   */
  updateWrite = (
    updateInferData?: UpdateInferData,
    updateKeyvalueData?: UpdateKeyValueData
  ) => {
    this.update(updateInferData, updateKeyvalueData);
    const newStyle = this.tokeyValueStyle();
    const newStyleString = this.toString(newStyle);
    this.writeNewStyle(newStyleString);
  };

  /**
   * 修改数据
   * @param inferData
   * @param keyvalueData
   */
  update = (
    updateInferData?: UpdateInferData,
    updateKeyvalueData?: UpdateKeyValueData
  ) => {
    if (updateInferData) {
      this.updateInferData(updateInferData);
    }
    if (updateKeyvalueData) {
      this.updateOrCreateKeyValueData(updateKeyvalueData);
    }
  };

  /**
   * 修改外推数据 外推数据只能修改,不能新增
   * @param inferData
   */
  updateInferData = (updateInferData: UpdateInferData) => {
    Object.keys(updateInferData).forEach((key) => {
      const findInfer = this.inferDatas.find((itemInfer) => {
        return itemInfer.key === key;
      });
      if (findInfer) {
        findInfer.value = updateInferData[key];
      } else {
        this.inferDatas.push({ key, value: updateInferData[key] });
      }
    });
  };

  /**
   * 修改KeyValue数据
   * @param keyvalueData keyValue数据
   */
  updateOrCreateKeyValueData = (updateKeyvalueData: UpdateKeyValueData) => {
    Object.keys(updateKeyvalueData).forEach((key) => {
      const newKey = this.updateKeyBySetting(key, this.themeSetting);
      this.keyValue[newKey] = updateKeyvalueData[newKey];
    });
  };
}

export default Theme;

使用

// 引入主题对象
import Theme from "./theme";
// 引入默认推断数据
import inferDatas from "./theme/inferDatas";
// 引入默认keyValue数据
import keyValueData from "./theme/keyValueData";
// 引入设置对象
import setting from "./theme/setting";
const app = createApp(App);
// 创建主题对象
const theme = new Theme(setting, keyValueData, inferDatas);
// 将主题对象放到全局变量中
app.config.globalProperties.theme = theme;

其他组件使用

import { reactive, getCurrentInstance } from "vue";
import type { ComponentInternalInstance } from "vue";
const { appContext } = getCurrentInstance() as ComponentInternalInstance;
const theme = appContext.config.globalProperties.theme;
theme.updateWrite({ primary: form.themeColor });

API

- 修改推断数据

theme.updateWrite({
    primary: "#409eff",
    success: "#67c23a",
    warning: "#e6a23c",
    danger: "#f56c6c",
    error: "#f56c6c",
    info: "#909399",
  });

- 修改键值类型数据 

theme.updateWrite(undefined, {
    "--el-menu-active-color": "red",
    "--el-menu-bg-color": "pink",
    "--el-menu-item-height": "22px",
  });

- 一起修改

theme.updateWrite(
    {
      primary: "#409eff",
      success: "#67c23a",
      warning: "#e6a23c",
      danger: "#f56c6c",
      error: "#f56c6c",
      info: "#909399",
    },
    {
      "--el-menu-active-color": "red",
      "--el-menu-bg-color": "pink",
      "--el-menu-item-height": "22px",
    }
  );

总结:

  主题数据可以存储在数据库中,通过接口查询后调用函数修改即可完成动态主题

  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值