一、思路分析
- 目前根据官方网站推荐的做法,最佳实践就是直接覆盖css变量。
// element-plus 官网指南 // document.documentElement 是全局变量时 const el = document.documentElement // const el = document.getElementById('xxx') // 获取 css 变量 getComputedStyle(el).getPropertyValue(`--el-color-primary`) // 设置 css 变量 el.style['--el-color-primary'] = 'red'
- 打开elementPlus官网 -> F12 ->查看元素样式。点击根元素也就是html,你会看到官方定义了很多的全局css变量,下图中这些变量就是主题色的css变量,我们要做的就是覆盖这些变量的值
但是问题来了,你会发现这些颜色都有色阶,要如何根据主色去计算出这些色阶颜色?
二、Sass mix函数JS翻译(最重要的一部分)
查看element-Plus样式源码 element-plus/theme-chalk/src/common/var.scss.
你会发现组件的颜色基本都基于这些全局的css变量去生成的,这就是为什么要覆盖css变量的原因。
核心代码如下,这段 scss 就是生成 --el-color-primari-light-3
等色阶颜色的。可以看到这里使用了一个sass颜色函数mix,这个函数是如何实现的暂时不管,先用js翻译下面代码。
@mixin set-color-type-light($type, $number) {
$colors: map.deep-merge(
(
$type: (
'light-#{$number}':
mix(
$color-white,
map.get($colors, $type, 'base'),
math.percentage(math.div($number, 10))
),
),
),
$colors
) !global;
}
// $colors.primary.light-i
// --el-color-primary-light-i
// 10% 53a8ff
// 20% 66b1ff
// 30% 79bbff
// 40% 8cc5ff
// 50% a0cfff
// 60% b3d8ff
// 70% c6e2ff
// 80% d9ecff
// 90% ecf5ff
@for $i from 1 through 9 {
@each $type in $types {
@include set-color-type-light($type, $i);
}
}
翻译后:
const node = document.documentElement;
// 前缀
const pre = "--el-color-primary";
// 主色调(可以使用el-color-picker绑定)
const color = "#409eff";
// 源码中的$color-white,也就是白色
const mixWhite = "#ffffff"
// 直接为根设置内联样式覆盖:root选择器的样式
node.style.setProperty(pre, color);
for (let i = 1; i < 10; i += 1) {
// 同理
node.style.setProperty(`${pre}-light-${i}`, mix(color, mixWhite, i * 0.1));
}
mix函数原理:mix(color1 = rgb(r1,g1,b1),color2 = rgb(r2,g2,b2),weight)
参数一二为颜色,参数3位权重百分比,权重就是颜色中每个数值所占的比重
r = (r1 * (1 - weight) + r2 * weight);
g = (g1 * (1 - weight) + g2 * weight);
b = (b1 * (1 - weight) + b2 * weight);
finalColor = rgb(r,g,b);
但是问题在于,js 中显然没法直接使用 rgb 颜色,所以要先将16进制颜色先转化成 rgb ,再计算,最后还要将得出的 rgb 颜色转化成16进制颜色(不转直接使用字符串的 rgb 也可以)。
const mix = (color1: string, color2: string, weight: number) => {
weight = Math.max(Math.min(Number(weight), 1), 0)
const r1 = parseInt(color1.substring(1, 3), 16)
const g1 = parseInt(color1.substring(3, 5), 16)
const b1 = parseInt(color1.substring(5, 7), 16)
const r2 = parseInt(color2.substring(1, 3), 16)
const g2 = parseInt(color2.substring(3, 5), 16)
const b2 = parseInt(color2.substring(5, 7), 16)
const r = Math.round(r1 * (1 - weight) + r2 * weight)
const g = Math.round(g1 * (1 - weight) + g2 * weight)
const b = Math.round(b1 * (1 - weight) + b2 * weight)
const _r = ('0' + (r || 0).toString(16)).slice(-2)
const _g = ('0' + (g || 0).toString(16)).slice(-2)
const _b = ('0' + (b || 0).toString(16)).slice(-2)
return '#' + _r + _g + _b
}
三、在你的项目中使用
1.安装依赖
npm i use-element-plus-theme
// 或者使用其他包管理器
pnpm i use-element-plus-theme
2.引入函数
import { useElementPlusTheme } from "use-element-plus-theme"
3.简单使用
const defaultTheme = ref('#405DFF')
const { changeTheme } = useElementPlusTheme(defaultTheme.value)
4.完整使用例子
<template>
<el-color-picker v-model="defaultTheme" @change="changeTheme" />
</template>
<script lang="ts" setup>
import { useElementPlusTheme } from 'use-element-plus-theme'
const defaultTheme = ref('#405DFF')
const { changeTheme } = useElementPlusTheme(defaultTheme.value)
</script>
四、源码以及预览
五、更新日志
2023-8-26
- 升级vite版本,修复打包后的文件后缀
- 仅生成3、6、7、8、9色阶的颜色
2022-8-20
element-plus 现在已经发布正式版本,重写了这篇文章,将核心逻辑封装成hook并发布npm
不一定是最完美的解决方式,但也是一种思路
本帖未经允许禁止转载