背景
Element
UI组件库相信大家一定都接触过。但是自定义主题颜色的需求有接触过的应该不多,至少我到今天是没有遇到类似的需求。之所以讲这个需求,是因为在我个人开发的开源项目中有做到这个需求,所以在这里和大家聊一聊我的实现。
CSS变量
在此之前我们需要先了解一下CSS变量
:它是自定义属性(有时候也被称作CSS变量
或者级联变量
)是由CSS
作者定义的,它包含的值可以在整个文档中重复使用。由自定义属性标记设定值,由var()
函数来获取值。
/* 设定值 */
:root {
--background-color: #FF0000;
}
/* 获取值 */
div {
background-color: var(--background-color);
}
Element主题色
由Element
官方文档我们可以它的整个组件库都是使用的CSS变量
实现的。并且它提供了四种方式来修改样式变量,具体方式可以参考官方文档。
我这边主要介绍的就是Element
通过js
控制CSS变量
设置的方式。那就会有两个问题:
如何通过js
获取到CSS变量
js
获取CSS变量
那就要介绍这个方法getComputedStyle()
:它返回一个对象,该对象在应用活动样式表并解析这些值可能包含的任何基本计算后报告元素的所有 CSS 属性的值。它有两个参数:第一个参数就是用于获取计算样式的Element
;第二个参数是可选参数,是指定一个要匹配的伪元素的字符串。返回的style
是一个实时的 CSSStyleDeclaration
对象,当元素的样式更改时,它会自动更新本身。CSSStyleDeclaration
对象中的getPropertyValue()
接口返回一个DOMString
,其中包含请求的CSS
属性的值。
const element = document.documentElement;
const style = window.getComputedStyle(element);
const value = style.getPropertyValue('`background-color`');
如何通过js
设置CSS变量
js
设置CSS变量
那就要介绍CSSStyleDeclaration
对象中的setProperty()
接口,它有三个参数:第一个参数是一个DOMString
,代表被更改的CSS
属性;第二个参数是一个可选参数也是一个DOMString
,含有新的属性值。如果没有指定,则当作空字符串;第三个参数是一个可选参数,是一个 DOMString
允许设置 "important" CSS 优先级。如果没有指定,则当作空字符串。
有了以上两个方法那我们就可以通过js
直接修改Element
的主题色了,简单实现一下:
<template>
<div class="container">
<el-button>Default</el-button>
<el-button v-for="item in types" :key="item" :type="item">{{ item }}</el-button>
<hr>
<el-radio-group v-model="radio" @change="radioChangeHandle">
<el-radio v-for="item in types" :key="item" :label="item" size="large">{{ item }}</el-radio>
</el-radio-group>
<br>
<el-color-picker v-model="color" @change="colorChangeHandle" />
</div>
</template>
<script setup>
import { ref } from 'vue'
const el = document.documentElement
const types = ref(['primary', 'success', 'info', 'warning', 'danger'])
const radio = ref('primary')
const color = ref('')
const radioChangeHandle = () => {
const value = getComputedStyle(el).getPropertyValue(`--el-color-${radio.value}`)
color.value = value
}
const colorChangeHandle = (value) => {
el.style.setProperty(`--el-color-${radio.value}`, value)
}
</script>
<style>
.container {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -100%);
width: 50%;
text-align: center;
}
</style>
通过以上方式修改了之后,所有的组件的颜色都已经修改了,并且已经达到了效果。但是其中还存在一个问题,以el-button
组件举例。每个按钮组件都会有个hover
的颜色,虽然显示的颜色已经修改了,但是hover
的颜色还是原来的颜色,这是一个严重的Bug
。原因是按钮的组件hover
的颜色使用了其CSS变量
。
颜色的HEX格式
颜色的HEX
格式是#
+六位数字/字母
,其中六位数字/字母
是一种十六进制的表达方式。这六位分别两个一组,从左到右分别表示红
、绿
、蓝
。00
表示最小,十进制是0
;FF
表示最大,十进制是255
。通俗点讲,某个颜色的数值越大,包含这个颜色就越多。如:#000000
-黑色、#FFFFFF
-白色、#FF0000
-红色、#00FF00
-绿色、#0000FF
-蓝色。
HEX格式颜色变亮、变暗
通过上文的介绍,我们可以知道Element
的hover
颜色变亮了,即颜色的数值变大了,那我们只要对要修改的颜色数值变大即可。那就需要用到以下的方法:
HEX格式转RGB格式
hex2Rgb(color) {
color = color.replace('#', '')
const result = color.match(/../g)
for (let i = 0; i < 3; i++) {
result[i] = parseInt(result[i], 16)
}
return result
}
RGB格式转HEX格式
rgb2Hex(r, g, b) {
const hexs = [r.toString(16), g.toString(16), b.toString(16)]
for (let i = 0; i < 3; i++) {
if (hexs[i].length === 1) {
hexs[i] = '0' + hexs[i]
}
}
const result = '#' + hexs.join('')
return result
}
使颜色变亮
lighten(color, level) {
const rgb = hex2Rgb(color)
for (let i = 0; i < 3; i++) {
rgb[i] = Math.floor((255 - rgb[i]) * level + rgb[i])
}
const result = rgb2Hex(rgb[0], rgb[1], rgb[2])
return result
}
使颜色变暗
darken(color, level) {
const rgb = hex2Rgb(color)
for (let i = 0; i < 3; i++) {
rgb[i] = Math.floor(rgb[i] * (1 - level))
}
const result = rgb2Hex(rgb[0], rgb[1], rgb[2])
return result
}
解决问题
有了上文的几个方法,我们就可以在修改主色的时候将对应的其他CSS变量
进行变亮或者变暗即可。一般这种主题都是会存储浏览器Storage
中,大家可以结合实际情况配合vuex
或者pinia
使用。这里就不展开聊了。贴上完整代码:
// utils.js
export function hex2Rgb(color) {
color = color.replace('#', '')
const result = color.match(/../g)
for (let i = 0; i < 3; i++) {
result[i] = parseInt(result[i], 16)
}
return result
}
export function rgb2Hex(r, g, b) {
const hexs = [r.toString(16), g.toString(16), b.toString(16)]
for (let i = 0; i < 3; i++) {
if (hexs[i].length === 1) {
hexs[i] = '0' + hexs[i]
}
}
const result = '#' + hexs.join('')
return result
}
export function lighten(color, level) {
const rgb = hex2Rgb(color)
for (let i = 0; i < 3; i++) {
rgb[i] = Math.floor((255 - rgb[i]) * level + rgb[i])
}
const result = rgb2Hex(rgb[0], rgb[1], rgb[2])
return result
}
export function darken(color, level) {
const rgb = hex2Rgb(color)
for (let i = 0; i < 3; i++) {
rgb[i] = Math.floor(rgb[i] * (1 - level))
}
const result = rgb2Hex(rgb[0], rgb[1], rgb[2])
return result
}
// index.vue
<template>
<div class="container">
<el-button>Default</el-button>
<el-button v-for="item in types" :key="item" :type="item">{{ item }}</el-button>
<hr>
<el-radio-group v-model="radio" @change="radioChangeHandle">
<el-radio v-for="item in types" :key="item" :label="item" size="large">{{ item }}</el-radio>
</el-radio-group>
<br>
<el-color-picker v-model="color" @change="colorChangeHandle" color-format="hex" :show-alpha="false" />
</div>
</template>
<script setup>
import { ref } from 'vue'
import { lighten, darken } from '@/utils'
const el = document.documentElement
const types = ref(['primary', 'success', 'info', 'warning', 'danger'])
const radio = ref('primary')
const color = ref(getComputedStyle(el).getPropertyValue(`--el-color-${radio.value}`))
const radioChangeHandle = () => {
const value = getComputedStyle(el).getPropertyValue(`--el-color-${radio.value}`)
color.value = value
}
const colorChangeHandle = (value) => {
el.style.setProperty(`--el-color-${radio.value}`, value)
for (let i = 1; i <= 9; i++) {
el.style.setProperty(`--el-color-${radio.value}-light-${ i }`, lighten(value, i / 10))
el.style.setProperty(`--el-color-${radio.value}-dark-${ i }`, darken(value, i / 10))
}
}
</script>
<style>
.container {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -100%);
width: 50%;
text-align: center;
}
</style>
以上就是本次分享的内容,附上源码。
感谢看官看到这里,如果觉得文章不错的话,可以给小生的几个开源项目点个Star⭐!
- 基于 Vue3 + Element-plus 管理后台基础功能框架
- 基于 Vue3 + Element-plus + websocket 即时聊天系统
- 基于 node 开发的后端服务:https://github.com/gmingchen/node-server