Element 组件库也支持暗黑模式啦

在这里插入图片描述

前言

  最近参考了Element Plus风格样式,修改了Element内部样式源文件,并发布了自定义的暗黑主题包 element-theme-darkplus

  效果预览 👉戳这里

背景

  Vue版本经历了23的大迭代,与之相关的一些生态系统也发生了变化和改动。

  例如组件库 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.scssvar.scss内保存了白垩主题全局的样式变量。然后拷贝到根目录下生成element-variables.scss变量基础文件
  • 命令行运行et,工具element-theme利用构建工具gulp和相关依赖,打包element-variables.scss内自定义的变量值到src目录下诸如index.scssalert.scss等文件中,编译生成可供使用的index.cssalert.css等等,共同构成一个主题包

  element-theme相关依赖项包括。

  • commander:可构建node的命令行程序,提供了命令行输入和参数解析等功能
  • ora:命令行loading加载效果插件
  • gulp:流的自动化代码构建系统
  • gulp-autoprefixer:为CSS文件添加浏览器前缀,以兼容不同浏览器
  • gulp-cssmin:压缩CSS
  • gulp-sassSASS编译
  • run-sequencegulp多任务按次序执行

  其优点在于。

  • 支持深层次的主题定制
  • 附加可选配置,修改变量更加灵活,本地构建速率快效率高

  缺点主要包括三种。

  第一element-theme强关联node版本,依赖关系为element-theme > gulp-sass 3.1.0 > node-sass 4.2.0 > node 12

node-sassnode对应版本可参考官方 Node version support policy

  一种解决方式是升级gulp-sass版本或者降级node版本,另一种方式是使用node版本适配的第三方库。

  第二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-cssgulp-sass内部依赖node-sass切换为 dart-sass,新增了CSS命名空间插件 gulp-css-wrap

  以下代码compileCss函数选中了src目录下所有.scss文件,gulp-sass编译构建,然后gulp-autoprefixer添加浏览器前缀,gulp-css-wrap新增类命名空间,最后gulp-clean-css压缩代码,输出至根目录下lib文件夹中。

// gulpfile.js
...

function compileCss() {
  return src('./src/*.scss')
    .pipe(sass.sync().on('error', sass.logError))
    .pipe(autoprefixer({ cascade: false }))
    .pipe(cssWrap({ selector: '.dark' }))
    .pipe(cleanCss())
    .pipe(dest('./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用于抽取暗黑主题中所有的颜色样式代码,核心的样式提取函数cssExtractor引用于插件 webpack-theme-color-replacerwebpack-theme-color-replacer可提取出CSS中指定关键字的代码。

  gulpfile.js新增颜色提取函数compileColorCss,其中renamegulp-rename 插件,用于修改文件名。

// gulpfile.js
...

function compileColorCss() {
  return src('./src/*.scss')
    .pipe(sass.sync().on('error', sass.logError))
    .pipe(autoprefixer({ cascade: false }))
    .pipe(cssWrap({ selector: '.dark' }))
    .pipe(extractColor({ keywords: ['#', 'rgb', 'transparent'] }))
    .pipe(cleanCss())
    .pipe(rename({ suffix: '.color' }))
    .pipe(dest('./lib'))
}

  以上代码大概率报错,原因是cssWrap()运行报错,css-wrap不能处理空文件。

  本地新增插件css-wrap,空文件跳过css-wrap处理。

// plugins/css-wrap.js
...

function cssWrap(options = {}) {
  return obj((file, _, callback) => {
    ...
    
    var contents = file.contents.toString()

    if (contents) {
      file.contents = new Buffer.from(wrap(contents, options))
    }

    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颜色提取插件,可最小化实现主题切换功能。拷贝了theme-chalk白垩主题的源文件,修复了问题文件语法错误,自主维护全局变量。

优化

关键字

  虽然extract-color插件能提取颜色类代码,但有种情况却不能合理提取出来,例如 menu.scss

// src/menu.scss
...

@include b(menu) {
  ...

  @include m(horizontal) {
    border-right: none;
  }
}

  代码border-right: none中并没有关键字#rgb或者transparent,插件提取不出来也很正常,那如何优化呢?

  拟采用关键字注释,但extract-color插件对注释并不敏感,这里使用var来折中处理。

// src/menu.scss
...

@include b(menu) {
  ...

  @include m(horizontal) {
    --ec-ignore-br: none;

    border-right: var(--ec-ignore-br);
  }
}

  关键字新增--ec-ignore,以上变量和var语句均能提取出来。

extractColor({ keywords: ['--ec-ignore', '#', 'rgb', 'transparent'] })

  你可能发现了白垩主题内部并未引入CSS变量,出于兼容性考虑,最新版本已弃用。在cssExtractor提取函数上我做了部分修改,思路效仿了 clean-css 的特殊注释。原理即在匹配到特殊注释/* extract-color ignore */时,CSS代码段仍然保留而不仅限于颜色值,衍生出了ignoreSpecialComments配置项(默认值false)。

extractColor({ keywords: ['#', 'rgb', 'transparent'], ignoreSpecialComments: false })

级联选择器

  cascader级联选择器稳定复现图标外现的问题。

在这里插入图片描述

  checkbox新增overflow: hidden可解决,但可能会产生其它的问题,暂时不解决。

.el-checkbox__inner {
  ...
  overflow: hidden;
}

进度条/评分

  评分组件 Rate 较为特殊,Element内部与颜色相关props均定义了默认值,且template模板中都采用内联样式,导致外部主题样式无法覆盖。

Props默认值
void-color#c6d1de
disabled-void-color#eff2f7
text-color#1f2d3d

  可传空值重置相应props值使主题生效。

<el-rate :value="3" show-text void-color="" text-color="" />

  进度条组件 Progress 类似,仅线形进度条支持,环形和仪表盘形不支持。

Props默认值
define-back-color#ebeef5
text-color#606266
<el-progress :percentage="20" define-back-color="" text-color="" />

  虽然两组件可以传空值重置属性以支持暗黑主题,而对于不关心此功能的同学,却不清楚为什么会传入诸如text-color=""这样的属性,这无疑在开发层面徒增了心智负担。

  嗯...样式么法解决RateProgress根本性问题了。

  重新定义RateProgress组件可以吗?还不能破坏原组件的拓展性和唯一性,则采用继承原始组件并在javascript里做了一个中间层,帮助用户初始置空相关颜色props

import ElementUI from 'element-ui'
import Darken from 'element-theme-darkplus/utils/darken'

Vue.use(ElementUI)
Vue.use(Darken(ElementUI))

🎉 写在最后

🍻伙伴们,如果你已经看到了这里,觉得这篇文章有帮助到你的话不妨点赞👍或 Star ✨支持一下哦!

手动码字,如有错误,欢迎在评论区指正💬~

你的支持就是我更新的最大动力💪~

GitHub / GiteeGitHub Pages掘金CSDN 同步更新,欢迎关注😉~

  • 6
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

DonV

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

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

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

打赏作者

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

抵扣说明:

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

余额充值