likes
comments
collection
share

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

作者站长头像
站长
· 阅读数 45

方案选择

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";

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

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

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

2. 覆盖 sass 内置 mix 函数

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

兼容性:

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

接下来需要定义一个 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);
}

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

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

可以看到编译后的组件样式都已经被替换成 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 变量中的默认值,再添加一个使用默认值的该属性。

最终实现效果:

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

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

Demo

项目地址

在线预览

转载自:https://juejin.cn/post/7242160008560902201
评论
请登录