当前位置:   article > 正文

elementUI 多套主题下 按需引入和全局引入 的换肤方案_element-ui/lib/theme-chalk

element-ui/lib/theme-chalk

elementUI多套主题下换肤的关键问题:按需引入时是不支持动态换肤的

网上也有相关问题的讨论# 按需引入elementui 导致动态换肤主题被默认主题替换

组件库按需引入的原理

组件库按需引入的原理是相似的:最终只引入指定组件和对应的样式

elementUI需要借助babel-plugin-component
ant-design-vue需要借助babel-plugin-import

babel-plugin-component为例
按需引入elementUI的Button组件:

import { Button } from 'element-ui'

Vue.component(Button.name, Button)
  • 1
  • 2
  • 3

编译后的文件(自动引入button.css):

import _Button from "element-ui/lib/button";
import _Button2 from "element-ui/lib/theme-chalk/button.css";
// base.css是公共的样式,如图标和淡入淡出的动画
import "element-ui/lib/theme-chalk/base.css";

Vue.component(_Button.name, _Button);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

通过按需引入来减少组件库的体积大小

组件库按需引入的换肤问题

为什么说组件库按需引入时是不支持动态换肤的?

原因1:按需引入,最终只会引入指定组件的样式,如上文的button.css,但button.css文件中只有一套样式
原因2:按需引入,组件的js和css是在路由懒加载时加载的,会导致动态换肤的主题样式会被按需引入的默认组件样式给覆盖掉

在这里插入图片描述

瓶颈:
elementUI和ant-design-vue均提供了多种自定义主题的方式,但无论何种方式,最终生成的button.css只有一套样式

换肤方案有哪些?

分为3类

  1. css样式覆盖,一般通过切换css选择器来实现
  2. 引入多套主题样式,通过 link 标签动态加载不同的主题样式
  3. 通过css var() 函数,定义颜色变量的方式

缺点:

  1. 方案一:引入的css需要包含多套样式,导致css的代码体积变大
  2. 方案二:通过link 标签加载css,需要明确的知道css文件的路径,否则会引入失败
  3. 方案三:兼容性不太好,IE全系版本都不兼容

在这里插入图片描述

不兼容IE,按需引入的换肤方案

通过方案三:css var() 来实现

优点: 没有副作用,性能好,也是腾讯、蚂蚁等主流组件库的通用换肤方案

步骤:
1、创建基础颜色变量theme.css文件

在这里插入图片描述

2、修改packages/theme-chalk/src/common/var.scss文件
将该文件的中定义的scss变量,替换成var()变量

在这里插入图片描述

3、修改packages/theme-chalk/gulpfile.js打包配置

根据上文按需引入elementUI的Button组件为例:
最终会引入button.css和base.css
import _Button2 from “element-ui/lib/theme-chalk/button.css”;
import “element-ui/lib/theme-chalk/base.css”;

需要将theme.css合并到base.css中,这样才能保证定义的颜色变量能加载到页面中

'use strict';

const {series, src, dest} = require('gulp');
const sass = require('gulp-dart-sass');
const autoprefixer = require('gulp-autoprefixer');
const cssmin = require('gulp-cssmin');
const concat = require('gulp-concat');

function compile() {
  return src('./src/*.scss')
    .pipe(sass.sync().on('error', sass.logError))
    .pipe(autoprefixer({
      overrideBrowserslist: ['ie > 9', 'last 2 versions'],
      cascade: false
    }))
    .pipe(cssmin())
    .pipe(dest('./lib'));
}

// 将 theme.css 和 lib/base.css合并成 最终的 base.css
function compile1() {
  return src(['./src/theme.css', './lib/base.css'])
    .pipe(concat('base.css'))
    .pipe(dest('./lib'));
}

// 将 base.css、 index.css 合并成 最终的 index.css
function compile2() {
  return src(['./lib/base.css', './lib/index.css'])
    .pipe(concat('index.css'))
    .pipe(dest('./lib'));
}

function copyfont() {
  return src('./src/fonts/**')
    .pipe(cssmin())
    .pipe(dest('./lib/fonts'));
}

exports.build = series(compile, compile1, compile2, copyfont);
  • 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
  • 36
  • 37
  • 38
  • 39
  • 40

4、打包后的button.css
在这里插入图片描述

5、切换主题时,通过给js给body设置新的颜色变量

changeTheme = () => {
  let styleVar = {
    '--color-white': '#ffffff',
    '--color-black': '#000000',
    '--color-primary': '#f5ba63',
    '--color-success': '#35b55f',
    '--color-warning': '#ffaa0e',
    '--color-danger': '#ee5640',
    '--color-info': '#e6e6e6',
    '--color-main': '#256dff',
    '--color-neutral': '#15181a',
    '--color-tip': '#2b2b2c',
    '--color-special': '#202020'
  }
  for (let i in styleVar) {
    document.body.style.setProperty(i, styleVar[i]);
  }
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

兼容IE,按需引入的换肤方案

通过方案一:css样式覆盖,通过切换css选择器来实现

要解决的问题:button.css文件中需要包含多套主题样式

换肤过程:
1、给css文件扩展命名空间,合并生成的css文件中包含多套主题样式
2、给body加上对应的类名,通过改变body的类名实现组件换肤

增加扩展命名空间的button.css文件
在这里插入图片描述

按需引入的换肤示例:

在这里插入图片描述

给css文件扩展命名空间

gulp-css-wrap 给css文件所有选择器添加命名空间

基本用法:
// 安装gulp 和 gulp-css-wrap
npm install gulp gulp-css-wrap

const gulp = require('gulp');
const cssWrap = require('gulp-css-wrap');

gulp.task('css-wrap', function () {
  return gulp.src(`./src/*.css`) // 选择文件
      .pipe(cssWrap({
        selector: '.LightTheme' /* 添加.LightTheme命名空间 */
      }))
      .pipe(gulp.dest('./src')) /* 存放的目录 */
})
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

转化前

.el-button + .el-button {
    margin-left: 10px
}
.el-button:focus, .el-button:hover {
    color: #409EFF;
    border-color: #c6e2ff;
    background-color: #ecf5ff
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

转化后

.LightTheme .el-button + .el-button {
    margin-left: 10px
}

.LightTheme .el-button:focus, .LightTheme .el-button:hover {
    color: #409EFF;
    border-color: #c6e2ff;
    background-color: #ecf5ff
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

批量给elementUI的css文件扩展命名空间

打开elementUI源码

原始的样式打包流程

package.json中的 build:theme 命令用来打包生成组件库的样式

1、执行node build/bin/gen-cssfilepackages/theme-chalk/src目录下生成index.scss文件
2、执行packages/theme-chalk/gulpfile.js 将scss文件编译成 css 并输出到packages/theme-chalk/lib 目录
3、执行cp-cli packages/theme-chalk/lib lib/theme-chalk,将packages/theme-chalk 拷贝到 lib/theme-chalk目录

// 构建样式: 生成在index.scss && 通过 gulp 将 scss 文件编译成css并输出到packages/theme-chalk/lib目录 && 将生成的样式copy到lib/theme-chalk
"build:theme": "node build/bin/gen-cssfile && gulp build --gulpfile packages/theme-chalk/gulpfile.js && cp-cli packages/theme-chalk/lib lib/theme-chalk",
  • 1
  • 2

增加扩展命名空间的打包流程

1、创建不同主题的scss文件

packages/theme-chalk/src/common目录下

创建浅色红色两种主题的scss文件
在这里插入图片描述

2、打包命令增加 浅色主题的 变量 cross-env THEME=Light
"build:theme": "node build/bin/gen-cssfile && cross-env THEME=Light gulp build --gulpfile packages/theme-chalk/gulpfile.js && cp-cli packages/theme-chalk/lib lib/theme-chalk"
  • 1
3、安装插件npm install gulp-css-wrap gulp-concat

修改 packages/theme-chalk/gulpfile.js

'use strict';

const {series, src, dest} = require('gulp');
const sass = require('gulp-dart-sass');
const autoprefixer = require('gulp-autoprefixer');
const cssmin = require('gulp-cssmin');
const concat = require('gulp-concat');
const cssWrap = require('gulp-css-wrap');
const fs = require('fs');

// 获取当前主题
let theme = process.env.THEME;
// 定义css扩展命名空间
let themeClass = `.${theme}Theme`;

// 删除var.scss, 重新创建var.scss,将LightTheme.scss的内容写入其中
// 用LightTheme.scss 替换 var.scss
function init() {
  fs.unlinkSync('./src/common/var.scss'); // 删除var.scss
  return src(`./src/common/${theme}Theme.scss`)
    .pipe(concat('var.scss')) // 定义生成的文件名
    .pipe(dest('./src/common'));
}

// 将 scss 编译成 css 并压缩,最后输出到对应主题的目录下
function compile1() {
  return src('./src/*.scss')
    .pipe(sass.sync().on('error', sass.logError))
    .pipe(autoprefixer({
      overrideBrowserslist: ['ie > 9', 'last 2 versions'],
      cascade: false
    }))
    .pipe(cssmin())
    .pipe(dest(`./${theme}`));
}

// 将Light/Light.css copy 到packages/theme-chalk/lib目录下
// 如果使用者不需要换肤,只需单独引入浅色主题.css即可(在全局引入时会用到)
function copyIndex() {
  return src(`./${theme}/index.css`)
    .pipe(concat(`${theme}.css`)) // 生成新的文件名, 如Light.css
    .pipe(dest('./lib'));
}

// 批量为css文件扩展命名空间 添加.LightTheme前缀
// base.css 和 icon.css 不需要换肤,不用添加前缀
function compile2() {
  // 排除不需要加扩展名base.css 和 icon.css的文件
  return src([`./${theme}/*.css`, `!./${theme}/base.css`, `!./${theme}/icon.css`])
    .pipe(cssWrap({
      selector: themeClass /* 添加的命名空间 */
    }))
    .pipe(cssmin())
    .pipe(dest(`./${theme}`));
}

exports.build = series(init, compile1, copyIndex, compile2);
  • 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
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
4、执行 npm run build:theme, 生成浅色主题的样式

在这里插入图片描述

展开Light/button.css, 浅色主题的扩展名已加上
在这里插入图片描述

注意:
打包之前,要删除packages/theme-chalk/src目录中内容为空的scss文件
否则会报以下错误
在这里插入图片描述

空的scss文件有

// 空的scss文件目录
['breadcrumb-item', 'button-group', 'checkbox-button', 'checkbox-group', 'collapse-item', 'infiniteScroll',
  'dropdown-item', 'dropdown-menu', 'form-item', 'infinite-scroll', 'menu-item', 'menu-item-group', 'submenu', 'tab-pane' ];
  • 1
  • 2
  • 3
5、一键打包生成多套主题

打包命令增加 红色主题的 配置和变量 cross-env THEME=Red

"build:theme": "node build/bin/gen-cssfile && cross-env THEME=Light gulp build --gulpfile packages/theme-chalk/gulpfile.js && cross-env THEME=Red gulp build --gulpfile packages/theme-chalk/gulpfile.js && cp-cli packages/theme-chalk/lib lib/theme-chalk"
  • 1

执行npm run build:theme, 一键生成浅色和红色 两种主题样式

生成LightRed两个文件夹

展开Red/button.css,红色主题的扩展名已加上
在这里插入图片描述

6、合并多套主题样式

合并的规则:将不同主题文件夹中,相同文件名的内容写入到一个css文件中

如Light/button.css 和 Red/button.css → 写入到最终的button.css中

打包命令增加合并的流程

"build:theme": "node build/bin/gen-cssfile && cross-env THEME=Light gulp build --gulpfile packages/theme-chalk/gulpfile.js && cross-env THEME=Red gulp build --gulpfile packages/theme-chalk/gulpfile.js && gulp build --gulpfile packages/theme-chalk/gulpfile.js && cp-cli packages/theme-chalk/lib lib/theme-chalk",
  • 1

修改 packages/theme-chalk/gulpfile.js,增加合并的流程

'use strict';

const {series, src, dest} = require('gulp');
const sass = require('gulp-dart-sass');
const autoprefixer = require('gulp-autoprefixer');
const cssmin = require('gulp-cssmin');
const concat = require('gulp-concat');
const cssWrap = require('gulp-css-wrap');
const fs = require('fs');

// 获取当前主题
let theme = process.env.THEME;
// 定义css扩展命名空间
let themeClass = `.${theme}Theme`;

// 删除var.scss, 重新创建var.scss,将Theme.scss的内容写入到var.scss
function init() {
  fs.unlinkSync('./src/common/var.scss'); // 删除var.scss
  return src(`./src/common/${theme}Theme.scss`)
    .pipe(concat('var.scss')) // 合并后的文件名
    .pipe(dest('./src/common'));
}

// 将 scss 编译成 css 并压缩,最后输出到对应主题的目录下
function compile1() {
  return src('./src/*.scss')
    .pipe(sass.sync().on('error', sass.logError))
    .pipe(autoprefixer({
      overrideBrowserslist: ['ie > 9', 'last 2 versions'],
      cascade: false
    }))
    .pipe(cssmin())
    .pipe(dest(`./${theme}`));
}

// 将Light/Light.css 或 Red/Red.css copy到lib目录下
function copyIndex() {
  return src([`./${theme}/index.css`])
    .pipe(concat(`${theme}.css`)) // 合并后的文件名, 如Light.css
    .pipe(dest('./lib'));
}

// 批量为css文件扩展命名空间 添加.LightTheme前缀
// base.css 和 icon.css 不需要换肤,不用添加前缀
function compile2() {
  // 排除不需要加扩展名的文件
  return src([`./${theme}/*.css`, `!./${theme}/base.css`, `!./${theme}/icon.css`])
    .pipe(cssWrap({
      selector: themeClass /* 添加的命名空间 */
    }))
    .pipe(cssmin())
    .pipe(dest(`./${theme}`));
}

function compile(path) {
  return src([`./Light/${path}.css`, `./Red/${path}.css`])
    .pipe(concat(`${path}.css`)) // 合并后的文件名
    .pipe(dest('./lib'));
}

let Components = require('../../components.json');
Components = Object.keys(Components);

// 添加index, 将Light/index.css和Red/index.css 合并成最终的index.css
Components.push('index');

let concatList = Components.map(item => () => {
  return compile(item);
});

// 拷贝 ./src/fonts 到 ./lib/fonts
function copyfont() {
  return src('./src/fonts/**')
    .pipe(cssmin())
    .pipe(dest('./lib/fonts'));
}

// 拷贝 base.css 和 icon.css 到 ./lib中
function copyBaseAndIcon() {
  return src(['./Light/base.css', './Light/icon.css'])
    .pipe(dest('./lib'));
}

// 有theme属性 走打包流程,否则 走合并流程
let gulpTask = theme ? [init, compile1, copyIndex, compile2] : [...concatList, copyfont, copyBaseAndIcon];
exports.build = series(...gulpTask);
  • 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
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86

执行npm run build:theme

lib/button.css中包含多套主题,达到最终效果
在这里插入图片描述

按需引入的打包问题已解决,下面说下全局引入的换肤方案

全局引入的换肤方案

不兼容IE

和按需引入的流程一样,不再赘述

兼容IE

展开最终生成的lib/theme-chalk目录
其中包含3个文件,Light.css 、 Red.css 、 index.css

Light.css包含浅色主题的全部样式
Red.css包含红色主题的全部样式
index.css包含 浅色主题 和 红色主题 的全部样式

打开index.css: 上半部分为浅色主题的样式,下半部分为红色主题的样式

在这里插入图片描述

当全局引入 需要换肤时
1、import ‘element-ui/lib/theme-chalk/index.css’;
2、给body加上对应的类名,通过改变body的类名实现组件换肤

当全局引入 不需要换肤时,可单独引入对应主题的样式
import ‘element-ui/lib/theme-chalk/Light.css’;
or
import ‘element-ui/lib/theme-chalk/Red.css’;

总结

从按需引入的原理入手,发现该模式不支持换肤的原因

分为兼容IE和不兼容IE两种模式进行处理

单个知识点总结:
1)学习了gulp 基于流的任务化构建工具
2):边读边写,减少频繁的 IO 操作,节约内存
3)掌握gulp的部分api : series, src, dest
4)了解gulp的相关插件 gulp-cssmingulp-concatgulp-css-wrap

参考链接

gulp-css-wrap工具的使用

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/很楠不爱3/article/detail/332254
推荐阅读