vue动态换肤(自定义主题)

动态换肤:Less+Vuex 实现主题切换与自定义
本文介绍如何在Vue项目中使用Less和Vuex实现全局换肤功能,包括创建全局样式变量、配置vue.config.js、定义主题模型和切换主题方法,以及在Home.vue中展示动态切换和自定义主题的完整步骤。

前言

有时候一个项目的主题并不能满足所有人的审美, 所以这个时候就需要换肤功能登场了。
下面是一个换肤demo, 思路很简单,定义一个全局css变量,然后在页面根元素获取变量并动态修改这个变量值即可完成。

效果

效果图

具体实现

1.准备项目

准备一个含有lessvuex的项目

2.安装插件

yarn add style-resources-loader vue-cli-plugin-style-resources-loader -D

3.新建global.less

global.less用于定义全局变量设置全局默认样式

  • 路径: src/theme/global.less (先建一个theme目录)
// 默认主题  因为会放在rgba()中  所以只需要rgb这三个值 使用rgba的好处是一个主题可以根据透明度配置更多相似主题的颜色
@themeColor: var(--themeColor, 100, 149, 237);

4.配置vue.config.js

vue.config.js是项目可选的配置文件

  • 路径: 与 package.json 同级
const path = require("path");

module.exports = {
  pluginOptions: {
    "style-resources-loader": {
      preProcessor: "less",
      patterns: [
        path.resolve(__dirname, "./src/theme/global.less"),
      ],
    },
  },
};

这里通过配置style-resources-loaderglobal.less设置的变量供项目中其他文件直接使用,而避免重复在每个样式文件中通过@import导入

5.新建model.js

model.js用于预设几套固定主题

  • 路径: src/theme/model.js
export const themes = {
  /* 默认主题 */
  default: {
    backgroundColor: `${100},${149},${237}`
  },
  /* 暗黑主题 */
  dark: {
    backgroundColor: `${0},${0},${0}`
  },
  /* 鲜红主题 */
  red: {
    backgroundColor: `${247},${72},${72}`
  },
  /* 草绿主题 */
  green: {
    backgroundColor: `${59},${235},${115}`
  }
};

这里我预设了四套固定主题

6.新建changeTheme.js

changeTheme.js用于定义修改主题的方法,修改样式与接收值来确定使用哪套主题的方法

  • 路径: src/theme/changeTheme.js
import {
    themes
} from "./model";

// 修改样式
const changeStyle = (obj) => {
    document.documentElement.style.setProperty(`--themeColor`, obj.backgroundColor);
};

// 改变主题的方法
export const setTheme = (themeName) => {
    const haveTheme = themes[themeName];
    // 有预设的主题名称
    if (haveTheme) {
        localStorage.setItem("primaryColor", haveTheme.backgroundColor);
        changeStyle(haveTheme);
    } else {
        let haveTheme = {
            backgroundColor: localStorage.getItem("primaryColor")
        };
        changeStyle(haveTheme);
    }
};

7.动态换肤功能实现

  • 路径: src/view/Home.vue
<template>
  <div class="home">
    <header>温情key</header>
    <main>
      <div class="box-item">
        <div class="title">css动画-animation</div>
        <div class="content">
          steps()可以传入两个参数,第一个是一个大于0的整数,他是将间隔动画等分成指定数目的小间隔动画,也就是指定每个阶段分为几步来展示动画
          ,然后根据第二个参数来决定显示效果。第二个参数设置后
          其实和step-start,step-end同义,在分成的小间隔动画中判断显示效果。
        </div>
      </div>
      <div class="box-item box2">
        <div class="title">css动画-animation</div>
        <div class="content">
          steps()可以传入两个参数,第一个是一个大于0的整数,他是将间隔动画等分成指定数目的小间隔动画,也就是指定每个阶段分为几步来展示动画
          ,然后根据第二个参数来决定显示效果。第二个参数设置后
          其实和step-start,step-end同义,在分成的小间隔动画中判断显示效果。
        </div>
      </div>
    </main>
    <footer><a href="https://www.wenqingkey.cn" style="text-decoration: none;color: #fff;">www.wenqingkey.cn</a></footer>

    <div class="changeTheme" :style="{ right: openThemeCom === true ? '0' : '-310px' }">
      <div @click="chooseTheme">{{ openThemeCom === true ? "→" : "←" }}</div>
      <div @click="setThemeHandle('default')"></div>
      <div @click="setThemeHandle('dark')"></div>
      <div @click="setThemeHandle('red')"></div>
      <div @click="setThemeHandle('green')"></div>
      <div>
        <input type="color" :value="defaultColor" @input="chooseColor"/>
      </div>
    </div>
  </div>
</template>

<script>
import { setTheme } from "../theme/changeTheme";

export default {
  data() {
    return {
      // 选择主题组件开闭
      openThemeCom: false,
    };
  },
  computed: {
    // 取色器 默认颜色
    defaultColor() {
      /* 将 rgb 转换为 hex 值 */
      let color = localStorage.getItem("primaryColor").split(',');
      let hex = "#" + ((1 << 24) + (Number(color[0]) << 16) + (Number(color[1]) << 8) + Number(color[2])).toString(16).slice(1);
      // console.log(hex);
      return hex;
    }
  },
  created() {
    this.initTheme();
  },
  methods: {
    /* 初始化主题 */
    initTheme() {
      setTheme();
    },
    /* 选择主题组件显示/隐藏 */
    chooseTheme() {
      this.openThemeCom = !this.openThemeCom;
    },
    /* 切换主题 */
    setThemeHandle(val) {
      setTheme(val);
    },
    /* 自定义主题 */
    chooseColor(val) {
      // hex 转换为 rgb
      let hex = val.target.value;
      let r = parseInt('0x' + hex.slice(1, 3));
      let g = parseInt('0x' + hex.slice(3, 5));
      let b = parseInt('0x' + hex.slice(5, 7));
      let newPrimaryColor = `${r},${g},${b}`;
      // console.log(newPrimaryColor);
      localStorage.setItem("primaryColor", newPrimaryColor);
      setTheme();
    },
  },
};
</script>
<style lang="less" scoped>
.home {
  widows: 100vw;
  height: 100vh;
  display: grid;
  grid-template-rows: 50px 1fr 50px;
  background: #eee;
  header,
  footer {
    width: 100vw;
    height: 50px;
    background: rgba(@themeColor, 1);
    text-align: center;
    line-height: 50px;
    font-size: 1.6em;
    color: #fff;
  }

  main {
    padding: 10px;
    .box-item {
      border: 1px solid #555;
    }
    .box2 {
      margin-top: 20px;
    }
    .title {
      padding: 6px;
      color: rgba(@themeColor, 1);
      font-size: 1.3em;
      border-bottom: 1px solid #555;
    }
    .content {
      padding: 10px;
      background: rgba(@themeColor, 0.2);
    }
  }

  .changeTheme {
    position: fixed;
    bottom: 70px;
    right: 0;
    height: 40px;
    width: 360px;
    background: #fff;
    display: grid;
    grid-template-columns: repeat(6, 1fr);
    column-gap: 5px;
    transition: 0.3s ease;
    div {
      position: relative;
    }
    div::after {
      display: inline-block;
      width: 60px;
      height: 20px;
      text-align: center;
      line-height: 20px;
      position: absolute;
      top: -22px;
      left: 0;
    }
    div:nth-child(1) {
      line-height: 40px;
      text-align: center;
    }
    div:nth-child(2) {
      background: rgb(100, 149, 237);
    }
    div:nth-child(2)::after {
      content: "默认";
    }
    div:nth-child(3) {
      background: rgb(0, 0, 0);
    }
    div:nth-child(3)::after {
      content: "暗黑";
    }
    div:nth-child(4) {
      background: rgb(247, 72, 72);
    }
    div:nth-child(4)::after {
      content: "鲜红";
    }
    div:nth-child(5) {
      background: rgb(59, 235, 115);
    }
    div:nth-child(5)::after {
      content: "草绿";
    }
    div:nth-child(6)::after {
      content: "自定义";
    }
  }
}
</style>

源码地址

gitee源码地址

Vue 3 应用中实现换肤功能,通常可以通过以下几种方案来完成,结合引用内容以及当前主流的实践方式,以下是详细的实现思路和技术要点: ### ### 使用 CSS 变量和动态主题切换 通过定义 CSS 变量并在运行时动态修改这些变量,可以实现换肤功能。例如,在 `:root` 中定义主题颜色,并根据用户选择或系统偏好设置不同的值: ```css :root { --primary-color: #409EFF; --background-color: #ffffff; } [data-theme="dark"] { --primary-color: #1a1a1a; --background-color: #121212; } ``` 在 JavaScript 中,可以通过修改 `document.documentElement.setAttribute('data-theme', 'dark')` 来切换主题[^2]。 ### ### 使用 Vuex 进行主题状态管理 为了更好地管理主题的状态,可以在 Vuex Store 中维护一个主题模块,保存当前的主题名称,并提供方法来更新这个状态。这样可以在多个组件之间共享和同步主题信息: ```javascript // src/store/modules/theme.js const themeModule = { state: () => ({ currentTheme: 'default' }), mutations: { SET_THEME(state, themeName) { state.currentTheme = themeName; } }, actions: { changeTheme({ commit }, themeName) { commit('SET_THEME', themeName); } } }; ``` 然后在组件中使用 `$store.dispatch('changeTheme', 'dark')` 来触发主题更改,并监听该状态的变化以更新 UI [^1]。 ### ### 利用自定义 Hook 管理主题逻辑 Vue 3 提供了 Composition API,允许创建可复用的逻辑单元。可以创建一个名为 `useThemeColor` 的自定义 Hook,用来处理主题相关的逻辑,如检测系统偏好、存储用户的选择等: ```javascript import { ref, watchEffect } from 'vue'; export default function useThemeColor() { const appearance = ref(localStorage.getItem('appearance') || 'auto'); const match = window.matchMedia("(prefers-color-scheme: dark)"); function followSystem() { const theme = match.matches ? 'dark' : 'light'; document.documentElement.setAttribute('data-theme', theme); } match.addEventListener('change', followSystem); watchEffect(() => { if (appearance.value === 'auto') { followSystem(); } else { document.documentElement.setAttribute('data-theme', appearance.value); } }); return { appearance }; } ``` 此 Hook 可以被任何组件导入并调用,从而获得当前的主题设置,并响应其变化[^2]。 ### ### 使用 Less/Sass 等 CSS 预处理器 对于更复杂的样式需求,可以利用 CSS 预处理器如 Less 或 Sass 来编写参数化的样式表。为每个主题定义一套变量,并通过类名应用相应的样式: ```less // color.less @import url('./theme.less'); .themea { .theme(); // 默认的样式 } .themeb { .theme(#409EFF, #fff); // 自定义颜色 } .themec { .theme(#909399, #fff); // 另一套颜色 } ``` 当需要切换主题时,只需更改元素上的类名即可[^3]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

温情key

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值