赞
踩
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)
编译后的文件(自动引入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:按需引入,最终只会引入指定组件的样式,如上文的button.css,但button.css文件中只有一套样式
原因2:按需引入,组件的js和css是在路由懒加载时加载的,会导致动态换肤的主题样式会被按需引入的默认组件样式给覆盖掉
瓶颈:
elementUI和ant-design-vue均提供了多种自定义主题的方式,但无论何种方式,最终生成的button.css只有一套样式
分为3类
css选择器
来实现link
标签动态加载不同的主题样式var()
函数,定义颜色变量的方式缺点:
link
标签加载css,需要明确的知道css文件的路径,否则会引入失败通过方案三: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);
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]); } };
通过方案一:css样式覆盖,通过切换css选择器
来实现
要解决的问题:button.css文件中需要包含多套主题样式
换肤过程:
1、给css文件扩展命名空间,合并生成的css文件中包含多套主题样式
2、给body加上对应的类名,通过改变body的类名实现组件换肤
增加扩展命名空间的button.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')) /* 存放的目录 */
})
转化前
.el-button + .el-button {
margin-left: 10px
}
.el-button:focus, .el-button:hover {
color: #409EFF;
border-color: #c6e2ff;
background-color: #ecf5ff
}
转化后
.LightTheme .el-button + .el-button {
margin-left: 10px
}
.LightTheme .el-button:focus, .LightTheme .el-button:hover {
color: #409EFF;
border-color: #c6e2ff;
background-color: #ecf5ff
}
打开elementUI源码
package.json中的 build:theme
命令用来打包生成组件库的样式
1、执行node build/bin/gen-cssfile
在packages/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",
packages/theme-chalk/src/common
目录下
创建浅色和红色两种主题的scss文件
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"
修改 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);
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' ];
打包命令增加 红色主题的 配置和变量 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"
执行npm run build:theme
, 一键生成浅色和红色 两种主题样式
生成Light和Red两个文件夹
展开Red/button.css
,红色主题的扩展名已加上
合并的规则:将不同主题文件夹中,相同文件名的内容写入到一个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",
修改 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);
执行npm run build:theme
lib/button.css中包含多套主题,达到最终效果
按需引入的打包问题已解决,下面说下全局引入的换肤方案
和按需引入的流程一样,不再赘述
展开最终生成的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-cssmin
、gulp-concat
、gulp-css-wrap
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。