Element 组件库也支持暗黑模式啦
前言
最近参考了Element Plus
风格样式,修改了Element
内部样式源文件,并发布了自定义的暗黑主题包 element-theme-darkplus。
效果预览 👉戳这里。
背景
Vue
版本经历了2
到3
的大迭代,与之相关的一些生态系统也发生了变化和改动。
例如组件库 Element 升级为了 Element Plus。
Element Plus
相较于Element
。
- 组件上新增加了 TreeSelect 树形选择、Text 文本等
- 性能上优化了用户体验,例如表格组件在大数据量的情况下渲染速度更快,输入框组件在输入响应上更加流畅等
- 开发上也支持
TypeScript
,能体验到更好的编码书写和类型检查 - 主题上特性最佳,新增了暗黑模式,可动态切换
那么为什么Element
无法支持暗黑模式呢?原因包括。
Element
支持自定义主题,却忽略了暗黑模式Element
创建初CSS
样式表中 var 技术并未成为标准Element
内部样式与scss
变量强关联,很难达到动态切换主题
GitHub
仓库 theme-chalk 保存了Element
样式源文件,其中src
目录下有很多的.scss
文件。如果想要Element
内部支持var
变量,几乎所有的.scss
都将修改,并且还要进行全面测试。如此庞大的工作量,对于仅剩社区维护的Element
来说,是根本不可能的。所以在Element
停止维护之前,都没有支持暗黑模式。
思路
现在功能就在这里,如何实现呢?
暗黑模式实际上就是一套Element
自定义主题包。而对于自定义主题,最好看看官方是否支持,而官网也确实提供了自定义主题的 实践方案。一共四种方法,可以进行不同程度的样式自定义。实际上可概括为两种方法,分别为浅层次和深层次的主题定制。
浅层次
即使用 在线主题编辑器 或者 主题编辑器 Chrome 插件。
原理上是浏览器修改了scss
变量值,在线服务实时打包并返回构建好的CSS
样式代码。
其优点在于。
- 交互式修改
Element
全局scss
样式变量 - 快捷实时地预览修改后的视觉效果
- 支持样式文件包上传、修改和下载
缺点在于。
- 在线服务请求较慢或者根本无法访问。大概率报错
Server Error 500
,当然也能访问,注意方式方法
哈 - 部分文件或组件内部颜色值固定,无法修改。例如 mixins.scss,滚动条滑块背景色
$--scrollbar-thumb-background
变量固定为了#b4bccc
深层次
即使用命令行主题工具 element-theme 搭配Element
白垩主题包 theme-chalk 工作。
原理上是在线主题编辑器的本地运行版,一共两个步骤,分别为创建变量文件和打包构建。
- 命令行运行
et -i
,工具element-theme
将抓取文件node_modules/element-ui/packages/theme-chalk/src/common/var.scss
,var.scss
内保存了白垩主题全局的样式变量。然后拷贝到根目录下生成element-variables.scss
变量基础文件 - 命令行运行
et
,工具element-theme
利用构建工具gulp
和相关依赖,打包element-variables.scss
内自定义的变量值到src
目录下诸如index.scss
、alert.scss
等文件中,编译生成可供使用的index.css
、alert.css
等等,共同构成一个主题包
element-theme
相关依赖项包括。
- commander:可构建
node
的命令行程序,提供了命令行输入和参数解析等功能 - ora:命令行
loading
加载效果插件 - gulp:流的自动化代码构建系统
- gulp-autoprefixer:为
CSS
文件添加浏览器前缀,以兼容不同浏览器 - gulp-cssmin:压缩
CSS
- gulp-sass:
SASS
编译 - run-sequence:
gulp
多任务按次序执行
其优点在于。
- 支持深层次的主题定制
- 附加可选配置,修改变量更加灵活,本地构建速率快效率高
缺点主要包括三种。
第一element-theme
强关联node
版本,依赖关系为element-theme
>
gulp-sass 3.1.0 >
node-sass 4.2.0 >
node 12
。
node-sass
与node
对应版本可参考官方 Node version support policy
一种解决方式是升级gulp-sass
版本或者降级node
版本,另一种方式是使用node
版本适配的第三方库。
node < 12
:element-themenode = 12.x
:element-themexnode >= 14
:element-theme-replace
第二theme-chalk
内部文件 var.scss 语法错误,导致拷贝生成的element-variables.scss
也有此问题。
// src/common/var.scss
...
$--breakpoints-spec: (
'xs-only' : (max-width: $--sm - 1),
'sm-and-up' : (min-width: $--sm),
'sm-only': "(min-width: #{$--sm}) and (max-width: #{$--md - 1})",
'sm-and-down': (max-width: $--md - 1),
'md-and-up' : (min-width: $--md),
'md-only': "(min-width: #{$--md}) and (max-width: #{$--lg - 1})",
'md-and-down': (max-width: $--lg - 1),
'lg-and-up' : (min-width: $--lg),
'lg-only': "(min-width: #{$--lg}) and (max-width: #{$--xl - 1})",
'lg-and-down': (max-width: $--xl - 1),
'xl-only' : (min-width: $--xl),
);
手动修改为。
// src/common/var.scss
...
$--breakpoints-spec: (
'xs-only' : (max-width: $--sm - 1),
'sm-and-up' : (min-width: $--sm),
'sm-only': (min-width: #{$--sm}) and (max-width: #{$--md - 1}),
'sm-and-down': (max-width: $--md - 1),
'md-and-up' : (min-width: $--md),
'md-only': (min-width: #{$--md}) and (max-width: #{$--lg - 1}),
'md-and-down': (max-width: $--lg - 1),
'lg-and-up' : (min-width: $--lg),
'lg-only': (min-width: #{$--lg}) and (max-width: #{$--xl - 1}),
'lg-and-down': (max-width: $--xl - 1),
'xl-only' : (min-width: $--xl),
);
第三theme-chalk
内部变量过于固定,例如 mix 函数强制混合$--color-white
变量,并不适用于暗黑主题。
// src/common/var.scss
...
$--color-primary-light-1: mix($--color-white, $--color-primary, 10%) !default; /* 53a8ff */
另外很多文件中存在白垩主题固定的颜色值,例如 upload.scss。
// src/upload.scss
...
@include b(upload) {
...
@include m(picture-card) {
background-color: #fbfdff;
border: 1px dashed #c0ccda;
...
}
...
}
自定义
两种方式都或多或少有缺点,解决起来也不是那么容易。
实际上深层次定制是有一些可取的部分的,解决掉附带的缺点,是不是就是可行的呢?
参考element-theme
我做了如下几个修改。
维护 scss 文件
theme-chalk
源文件自身存在语法错误,那根据源文件生成新文件就毫无意义了。干脆省略element-theme
创建变量文件那一步,拷贝theme-chalk
的最新源文件 src 到根目录下。然后修复语法错误,自行维护src
源文件。
升级依赖项
升级所有依赖项,压缩插件gulp-cssmin
替换为gulp-clean-css
,gulp-sass
内部依赖node-sass
切换为 dart-sass,新增了CSS
命名空间插件 gulp-css-wrap。
以下代码compile
函数选中了src
目录下所有.scss
文件,gulp-sass
编译构建,然后gulp-autoprefixer
添加浏览器前缀,gulp-css-wrap
新增类命名空间,最后gulp-clean-css
压缩代码,输出至根目录下lib
文件夹中。
// gulpfile.js
...
function compile() {
return src('./src/*.scss')
.pipe(sass.sync().on('error', sass.logError))
.pipe(autoprefixer({ cascade: false }))
.pipe(cssWrap({ selector: '.dark' }))
.pipe(cleanCss())
.pipe('./lib')
}
以button
为例,原样式代码为。
.el-button {
...
background: #FFF;
}
新增命名空间样式代码为。
.dark .el-button {
...
background: #FFF;
}
修改源文件
暗黑主题风格参照了Elemenet Plus
,修改src
下所有源文件颜色变量值。内部固定颜色值替换为已有变量,或者新增全局变量。
思路较简单,但工作量极大。
编写 gulp 插件
若一个项目有白垩和暗黑两种主题,也能动态切换,我们先看看白垩的部分样式代码。
.el-button {
display: inline-block;
line-height: 1;
white-space: nowrap;
cursor: pointer;
background: #FFF;
border: 1px solid #DCDFE6;
color: #606266;
...
}
然后看看暗黑的对应样式代码。
.dark .el-button {
display: inline-block;
line-height: 1;
white-space: nowrap;
cursor: pointer;
background: transparent;
border: 1px solid #4c4d4f;
color: #cfd3dc;
...
}
发现问题没有,主题切换只是颜色值改变,但是却额外维护了重复的样式代码,比如cursor: pointer
等。
我们完全可以将白垩主题作为基础,而暗黑部分仅仅引入颜色样式即可。可能像如下方式引入,index.color.css
中仅仅只有颜色代码,没有其它CSS
代码。
import 'element-ui/lib/theme-chalk/index.css';
import 'element-theme-darkplus/lib/index.color.css';
新增插件extract-color.js
用于抽取暗黑主题中所有的颜色样式代码,核心的样式提取函数cssExtractor
引用于插件 webpack-theme-color-replacer,webpack-theme-color-replacer
可提取出CSS
中指定关键字的代码。
gulpfile.js
修改为如下,其中rename
为 gulp-rename 插件,用于修改文件名。
// gulpfile.js
...
function compile() {
return src('./src/*.scss')
.pipe(sass.sync().on('error', sass.logError))
.pipe(autoprefixer({ cascade: false }))
.pipe(cssWrap({ selector: '.dark' }))
.pipe(cleanCss())
.pipe('./lib')
.pipe(extractColor({ keywords: ['#', 'rgb', 'transparent'] }))
.pipe(cleanCss())
.pipe(rename(path => (path.basename += '.color')))
.pipe(dest('./lib'))
}
以上代码大概率报错,原因是cssWrap()
运行报错,css-wrap
不能处理空文件。
本地新增插件css-wrap.js
,利用try...catch
捕获css-wrap
插件发生的错误。
// plugins/css-wrap.js
...
function cssWrap(options = {}) {
return obj((file, _, callback) => {
...
var contents = ''
try {
contents = wrap(file.contents.toString(), options)
} catch { }
file.contents = new Buffer.from(contents)
callback(null, file)
})
}
新增主题预览
element-theme-darkplus
内很多配置文件都是用于开发阶段格式化.vue
和.js
的,更多 详细参考。
docs/index.html
文件中以cdn
方式引入了 vue2-sfc-loader,非脚手架页面也能解析.vue
文件了。
cdn
引入了多个依赖包,导致首屏加载慢,长时间处于白屏状态,这里复用了 Ant Design Pro Vue 加载动画效果。
小结
element-theme-darkplus
参考了element-theme
进一步优化了gulpfile.js
逻辑部分,新增了extract-color.js
颜色提取插件,可最小化实现主题切换功能。拷贝了theme-chalk
白垩主题的源文件,修复了问题文件语法错误,自主维护全局变量。
优化
关键字
虽然extract-color.js
插件能提取颜色类代码,但有种情况却不能合理提取出来,例如 menu.scss。
// src/menu.scss
...
@include b(menu) {
...
@include m(horizontal) {
border-right: none;
}
}
代码border-right: none
中并没有关键字#
、rgb
或者transparent
,插件提取不出来也很正常,那如何优化呢?
拟采用关键字注释,但extract-color.js
插件对注释并不敏感,这里使用var
来折中处理。
// src/menu.scss
...
@include b(menu) {
...
@include m(horizontal) {
--ec-ignore-br: none;
border-right: var(--ec-ignore-br);
}
}
关键字新增--ec-ignore
,以上变量和var
语句均能提取出来。
级联选择器
cascader
级联选择器稳定复现图标外现的问题。
checkbox
新增overflow: hidden
可解决,但可能会产生其它的问题,暂时不解决。
.el-checkbox__inner {
...
overflow: hidden;
}
下拉菜单
dropdown
下拉菜单type=default
或不指定type
时,由于下拉按钮背景色为transparent
,将导致左边按钮右边框浮现,分割线呈竖直拉通状态。
暂未解决。
进度条 评分
进度条和评分组件是一类问题,组件上颜色相关的prop
均是组件内 内联控制,而根据类名修改将不生效。
进度条部分使用!important
避开,但部分颜色无法自定义。
评分组件也无法修改,两者问题都较大,暂未解决。
🎉 写在最后
🍻伙伴们,如果你已经看到了这里,觉得这篇文章有帮助到你的话不妨点赞👍或 Star ✨支持一下哦!
手动码字,如有错误,欢迎在评论区指正💬~
你的支持就是我更新的最大动力💪~
GitHub / Gitee、GitHub Pages、掘金、CSDN 同步更新,欢迎关注😉~
转载自:https://juejin.cn/post/7254372820172030011