不多废话,直入主题 。文末会附上git代码链接

自定义主题切换

概述

一般主题切换为两种需求:主题色切换/日夜切换。

主题色切换:(基于elementUI)

在这里插入图片描述
1、最开始想的是直接通过 el.style.setProperty(‘--el-color-primary’, colorValue)修改主题色变量,可以实现最基本的修改,但是修改后发现像elementUI主题色是会分为多个层级,比如鼠标移入会变得更亮等,并且在夜间模式 主题色也会有区别
2、由1分析到需要用到css的mix()函数去将颜色混合成多个层级,然后如element这样--el-color-primary-light-xx设置覆盖。因为我需要动态设置所以需要通过JS调用mix()函数,但是发现在js中无法操作css的mix()函数。在网上也没有找到方法
3、用js手动实现mix()函数的混合颜色分级算法,在网上看到了gradient算法,由此可以实现两种颜色混合分级
如:亮色下 elementUI的主题色409eff#ffffff混合分10级后就刚好是对应的--el-color-primary-light- 1~9
算法如下:

function gradientColor(this: any, startColor: any, endColor: any, step: any) {
    let startRGB = gradientColor.prototype.colorRgb(startColor);//转换为rgb数组模式
    let startR = startRGB[0];
    let startG = startRGB[1];
    let startB = startRGB[2];
    let endRGB = gradientColor.prototype.colorRgb(endColor);
    let endR = endRGB[0];
    let endG = endRGB[1];
    let endB = endRGB[2];
    let sR = (endR - startR) / step;//总差值
    let sG = (endG - startG) / step;
    let sB = (endB - startB) / step;
    var colorArr = [];
    for (var i = 0; i < step; i++) {
        //计算每一步的hex值 
        var hex = gradientColor.prototype.colorHex('rgb(' + parseInt((sR * i + startR)) + ',' + parseInt((sG * i + startG)) + ',' + parseInt((sB * i + startB)) + ')');
        colorArr.push(hex);
    }
    return colorArr;
}
// 将hex表示方式转换为rgb表示方式(这里返回rgb数组模式)
gradientColor.prototype.colorRgb = function (sColor: any) {
    var reg = /^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/;
    var sColor = sColor.toLowerCase();
    if (sColor && reg.test(sColor)) {
        if (sColor.length === 4) {
            var sColorNew = "#";
            for (var i = 1; i < 4; i += 1) {
                sColorNew += sColor.slice(i, i + 1).concat(sColor.slice(i, i + 1));
            }
            sColor = sColorNew;
        }
        //处理六位的颜色值
        var sColorChange = [];
        for (var i = 1; i < 7; i += 2) {
            sColorChange.push(parseInt("0x" + sColor.slice(i, i + 2)));
        }
        return sColorChange;
    } else {
        return sColor;
    }
};
// 将rgb表示方式转换为hex表示方式
gradientColor.prototype.colorHex = function (rgb: any) {
    var _this = rgb;
    var reg = /^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/;
    if (/^(rgb|RGB)/.test(_this)) {
        var aColor = _this.replace(/(?:(|)|rgb|RGB)*/g, "").split(",");
        var strHex = "#";
        for (var i = 0; i < aColor.length; i++) {
            var hex: any = Number(Number(aColor[i]).toString(16));
            hex = hex < 10 ? 0 + '' + hex : hex;// 保证每个rgb的值为2位
            if (hex === "0") {
                hex += hex;
            }
            strHex += hex;
        }
        if (strHex.length !== 7) {
            strHex = _this;
        }
        return strHex;
    } else if (reg.test(_this)) {
        var aNum = _this.replace(/#/, "").split("");
        if (aNum.length === 6) {
            return _this;
        } else if (aNum.length === 3) {
            var numHex = "#";
            for (var i = 0; i < aNum.length; i += 1) {
                numHex += (aNum[i] + aNum[i]);
            }
            return numHex;
        }
    } else {
        return _this;
    }
}
export default { gradientColor }

4、实现色彩分级后就简单了,按照elementUI的色彩层级依次覆盖

//设置主题色
const setTheme = (colorName: string, color: string): void => {
    const el = document.documentElement
    const oldColor = getComputedStyle(el).getPropertyValue(`--c-base`).trim()
    let gradient: any = getColorArr.gradientColor(color, oldColor, 10);
    gradient.forEach((item: string, index: number) => {
        if (index === 0) el.style.setProperty(`${colorName}`, item)
        if (index === 9) return
        el.style.setProperty(`${colorName}-light-${index + 1}`, item)
    })
}

5、完全自定义修改主题色就成功啦,需要注意的是如果网站有日夜主题切换功能,日间模式是用白色和主题色混合分为十级,而夜间模式是用黑色和主题色混合分十级

日夜切换

主题色切换只用改变主题的颜色 背景之类的无需改变,但是日夜切换要将所有的色调取反,用上面的方法就很显笨拙了
1、默认用了element-plus (如果没用按照下面步骤也完全可以实现),elementUI的包里内置的就有一套夜间模式css, 需要再main.ts中引用import 'element-plus/theme-chalk/dark/css-vars.css',element-plus文档描述直接在html标签上加一个class='dark'就可以实现了

2、网站上没有用到element-plus的元素用第一步的设置是无法实现的,因此需要自己手动定义css变量并修改

//定义css变量
:root {
    --c-base: #ffffff;
    --c-text-common: #303133;
    --c-text-regular: #606266;
    --c-text-disabled: #C0C4CC;

    --c-menu-text: white;
    --el-color-primary: aqua;

    --c-bg-body: #E6E8EB;
    --c-bg-box: white;

    --c-bg-header:rgba(255,255,255,.5);

    --c-border: #CDD0D6;

    --c-shadow: rgba(0, 0, 0, 0.5);
}

:root[theme='dark'] {
    --c-base: #000000;
    --c-text-common: #E5EAF3;
    --c-text-regular: #A3A6AD;
    --c-text-disabled: #6C6E72;

    --c-menu-text: white;
    --el-color-primary: #409eff;

    --c-bg-body: #292a2d;
    --c-bg-box: #424243;

    --c-bg-header:rgba(0,0,0,.5);


    --c-border: #636466;

    --c-shadow: rgba(255, 255, 255, 0.5);
}

3、在页面上使用就不要再直接给定颜色了,而是用变量表示

 //base.css
 color: var(--color-text);
 background: var(--color-background);
 transition: color 0.5s, background-color 0.5s;

4、主题切换操作

const change_moon_sun = (): void => {
    ifSun = !ifSun
    if (ifSun) {
        utils.setStyle('.switch-moon-sun', 'left', '0px')//日月切换动画
        document.documentElement.removeAttribute('theme') //原生dom色彩改变
        document.documentElement.classList.remove('dark')//elementUi改变
        utils.setTheme('--el-color-primary', localStorage.getItem('--el-color-primary') || '#409eff')//改变主题渐变色
    }
    else {
        utils.setStyle('.switch-moon-sun', 'left', '-' + domWidth)
        document.documentElement.setAttribute('theme', 'dark')
        document.documentElement.classList.add('dark')
        utils.setTheme('--el-color-primary', localStorage.getItem('--el-color-primary') || '#409eff')

    }
}

后言

这个主题切换是我在搭建个人主页时用到的一个功能,用的vue3+ts+element-plus,但是文中的修改主题方法其实与框架无关,主要是css变量和dom操作

个人demo的git链接

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐