使用 CSS 变量实现 ElementUI 动态主题切换

方案选择

Element UI 本身是支持主题定制功能的,官方提供了四种方法实现自定义主题,本质上的实现思路都是覆盖组件库本身预定义的 sass 变量,然后编译出一个新的 css 文件。

虽然这样可以满足大部分场景,但还要有一些局限性:

  1. 当主题色是由用户在页面动态选择时,由于无法预知会有什么色值,就没办法通过提前编译好一些 css 文件进行单纯的样式文件切换实现了,就必须在页面进行 sass 在线编译。
  2. 当项目选择使用 css 变量作为项目主题切换的技术方案时,就会导致项目本身和 Element UI 组件库实现主题切换的技术方案不一致。

基于上述两个局限性,我们尝试让 Element UI 也采用 css 变量的方案来实现动态主题切换功能。

实现思路

1. 使用 css 变量覆盖 sass 变量

首先我们还是采用覆盖组件库 sass 变量的方式,但是不是用固定的色值去覆盖,而是使用 css 变量

// element-variables.scss

$--color-primary: var(--color-primary, #409EFF);


$--font-path: '~element-ui/lib/theme-chalk/fonts';

@import "~element-ui/packages/theme-chalk/src/index";

但是直接采用这种方式修改会导致编译报错:

1.png

问题显而易见,Element UI 中使用了很多 sass 的内置函数 mix,这个函数的参数只支持确定的色值,不支持 css 变量。

2. 覆盖 sass 内置 mix 函数

mix 函数没办法支持 css 变量,那么 css 中有函数可以代替 mix 的功能么?确实是有的:color-mix

兼容性:

2.png

接下来需要定义一个 mix 函数去覆盖 sass 内置的 mix 函数:

// element-variables.scss

// 覆盖 sass 内置的 mix 函数,使用 css 的 color-mix 函数代替
@function mix($color1, $color2, $p: 50%) {
  @return color-mix(in srgb, $color1 $p, $color2);
}

覆盖后就可以正常进行编译了。

3.png

可以看到编译后的组件样式都已经被替换成 css 变量和 color-mix 函数,这样就可以通过直接在页面注入和修改 css 变量值进行动态主题切换了。

兼容性处理

由于 css 变量color-mix 函数的兼容性问题,我们需要设置一个兜底方案,在浏览器不支持的情况下可以展示默认的颜色,我们可以通过自定义一个 postcss 插件来实现这个兜底方案。

// postcss-plugin.js
const Color = require('color')


module.exports = (opts = {}) => {
  return {
    postcssPlugin: 'POSTCSS-PLUGIN',
    Declaration (decl, { Declaration }) {
      let newVal = decl.value

      const varArr = getVar(decl.value)
      if (varArr) {
        varArr.forEach(i => {
          const _i = i.match(/,(.*)\)/)
          if (_i) newVal = newVal.replace(i, _i[1].trim())
        })
      }

      const mixArr = getColorMix(newVal)
      if (mixArr) {
        mixArr.forEach(i => {
          const _i = getColorMixDefault(i)
          if (_i) newVal = newVal.replace(i, _i)
        })
      }

      if (newVal !== decl.value) {
        decl.before(new Declaration({ prop: decl.prop, value: newVal }))
      }
    }
  }
}

function getVar (value) {
  return value.match(/var\([^(]+(\([^(]+\))?[^(]*\)/g)
}

function getColorMix (value) {
  return value.match(/color-mix\([^(]+(\([^(]+\))?([^(]+\([^(]+\))?[^(]*\)/g)
}

function getColorMixDefault (value) {
  const arr = value.match(/[^,]+,([^(]+|[^(]+\([^)]+\)[^,]*),(.*)\)/)
  if (arr) {
    const index = arr[1].lastIndexOf(' ')
    const p = arr[1].slice(index).trim().replace('%', '') / 100
    const color1 = Color(arr[1].slice(0, index).trim())
    const color2 = Color(arr[2].trim())
    // 使用 color.js 的 mix 方法提前混出默认值
    return color2.mix(color1, p).hex()
  } else {
    return null
  }
}

module.exports.postcss = true

这个插件实现功能其实很简单,就是在遇到属性值有 css 变量和 color-mix 函数的属性时,取到 css 变量中的默认值,再添加一个使用默认值的该属性。

最终实现效果:

4.png

这样在遇到兼容性有问题的浏览器时就会默认使用固定色值。

Demo

项目地址

在线预览

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MYolTM2L' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片