一键换肤方案总结

3/8/2022 CSS

# 换肤方案1::root + css变量

:root 选择器匹配文档根元素。

在 HTML 中,根元素始终是 html 元素。

:root {
  --bg-color-0: #fff;
  --bg-color-1: #fff;
  --text-color: #333;
  --grey-1: #1c1f23;
}
:root .dark {
  --bg-color-0: #16161a;
  --bg-color-1: #35363c;
  --text-color: #fff;
  --grey-1: #f9f9f9;
}
body {
  background-color: var(--bg-color-0);
}
.content {
  background-color: var(--bg-color-1);
  color: var(--text-color);
}
.content button {
  background-color: var(--bg-color-1);
  border: 1px solid var(--grey-1);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

切换:

document.body.classList.remove('dark')
document.body.classList.add('dark')
1
2

# 换肤方案2:css变量 + css-vars-ponyfill兼容IE

  • utils/theme.js 文件
export const themeVars = {
  light: {
    '--subMenuBg': '#1f2d3d',
    '--pageBgColor': '#EAEFF8',
    '--page-container-bgColor': '#FFFFFF',
    '--description-title': '#333333',
  },
  dark: {
    '--subMenuBg': '#fff',
    '--pageBgColor': '#141837',
    '--page-container-bgColor': '#1B2242',
    '--description-title': 'rgba(255,255,255,0.80)',
  }
};

import cssVars from 'css-vars-ponyfill';
export const initTheme = theme => {
  cssVars({
    watch: true,  // 当添加删除或修改其<link>或<style>元素的禁用或href属性时,ponyfill将自行调用
    variables: themeVars[theme],  // variables 自定义属性名/值对的集合
    onlyLegacy: false   // false  默认将css变量编译为浏览器识别的css样式
  });
  return themeVars[theme];  //  返回变量,为了能够在.vue文件中的script标签中使用
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
  • 切换换肤按钮
<el-switch
  v-model="currentTheme"  active-text="白天" inactive-text="黑夜"
  active-value="light" inactive-value="dark" @change="changeTheme"
></el-switch>
<script>
methods: {
  created () {
    this.currentTheme = localStorage.getItem('dataTheme') || 'light';
    this.changeTheme()
  },
  ...mapMutations({ setCurrentThemeObj: 'theme/setCurrentThemeObj' }),
  changeTheme () {
  	localStorage.setItem('dataTheme', this.currentTheme);
    const currentThemeObj = initTheme(this.currentTheme);
    this.setCurrentThemeObj(currentThemeObj);
  }
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
  • store:存储主题信息,以便在.vue文件中的script标签中使用
const state = { currentThemeObj: {} };
const getters = {
  currentThemeObj: state => state.currentThemeObj;
};
const mutations = {
  setCurrentThemeObj (state, currentThemeObj) {
    state.currentThemeObj = currentThemeObj;
  }
};
export default { namespaced: true, state, getters, mutations };
1
2
3
4
5
6
7
8
9
10
  • echart 换肤 --- .vue文件中的script标签中使用
  computed: { ...mapGetters({ currentThemeObj: 'currentThemeObj' }) },
  watch: { currentThemeObj () { this.setOption(); } },
  methods: {
    getOption () {
      const fontColor = this.currentThemeObj['--alarmsRecentMonthFontColor'];
    }
  }
1
2
3
4
5
6
7

# 换肤方案3:scss变量@mixin混入覆盖

  • ./src/style/theme/index.scss
$theme-light: ( overview-row-bgcolor: #fff );
$theme-dark: ( overview-row-bgcolor: #1b2242);
$themes: ( light: $theme-light,  dark: $theme-dark, );
$theme-map: null;

// 第三步: 定义混合指令, 切换主题,并将主题中的所有规则添加到theme-map中
@mixin themify() {
  @each $theme-name, $map in $themes {
    // & 表示父级元素  !global 表示覆盖原来的
    [data-theme="#{$theme-name}"] & {
      $theme-map: () !global;
      // 循环合并键值对
      @each $key, $value in $map {
        $theme-map: map-merge( $theme-map, ( $key: $value, ) ) !global;
      }
      // 表示包含 下面函数 themed()
      @content;
    }
  }
}
@function themed($key) { @return map-get($theme-map, $key); }
// mixin主题颜色
@mixin bgColor($color) {
  @include themify { background-color: themed($color); }
}
@mixin borderColor($color) {
  @include themify { border-color: themed($color); }
}
@mixin textColor($color) {
  @include themify { color: themed($color); }
}
// @mixin text-color($key) {
//   color: map-get($colors-light, $key);
//   [data-theme="dark"] & { color: map-get($colors-dark, $key); }
// }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
  • vue.config.js
const fs = require("fs");
const webpack = require("webpack");
// 获取主题文件名
const themeFiles = fs.readdirSync("./src/style/theme");
let ThemesArr = [];
themeFiles.forEach(function (item, index) {
  let stat = fs.lstatSync("./src/style/theme/" + item);
  if (stat.isDirectory() === true) {
    ThemesArr.push(item);
  }
});
module.exports = {
  css: { loaderOptions: {
      scss: { // 注意: 在sass-loader v8 中,这个选项是prependData
        additionalData: `@import "./src/style/theme/index.scss";`,
      }, },
  },
  configureWebpack: (config) => {
    return { plugins: [
        new webpack.DefinePlugin({        // 定义全局变量的插件
          THEMEARR: JSON.stringify(ThemesArr),
          THEMEFILES: JSON.stringify(themeFiles),
        }),
      ],};
  },
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
  • APP.vue
<template>
  <div id="app"> <router-view data-theme="light-theme" /> </div>
</template>
<script>
export default {
  mounted() {
    console.log("ThemesArr", THEMEARR, "THEMEFILES", THEMEFILES);
    document.getElementsByTagName("body")[0].setAttribute("data-theme", "default");
  },
};
</script>
1
2
3
4
5
6
7
8
9
10
11
  • Home.vue--切换主题
      currentTheme: "default",
      currentThemeIndex: 0,
      themeValue: [],

  methods: {
    onConfirm(currentTheme) {
      this.currentTheme = currentTheme;
      this.currentThemeIndex = this.themeValue.findIndex(
          (theme) => theme === currentTheme );
      document.getElementsByTagName("body")[0]
        .setAttribute("data-theme", THEMEARR[this.currentThemeIndex]);
    },
  },
  mounted() {
    this.themeValue = THEMEARR;
    this.currentThemeIndex = this.themeValue.findIndex(
      (theme) => theme === "default" );
    this.currentTheme = this.themeValue[this.currentThemeIndex];
  },
<style lang="scss">
.home {
  .t-home-info {
    @include themify() {
      color: themed("color");
      font-weight: themed("font-weight");
      font-size: themed("font-size");
    }
  }
}
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

这种方式切换主题,会有卡顿,切换速度较慢。 方案二较好!!亲测!!