前端如何实现更换项目主题色的功能?

1、场景

有一个换主题色的功能,如下图:

切换颜色后,将对页面所有部分的色值进行重新设置,符合最新的主题色。

2、实现思路

因为色值比较灵活,可以任意选取,所以最好的实现方式是,根据设置的色值,拼接相应的 style 标签代码,对样式进行覆盖。

3、实现步骤

3.1、获取深浅色

当前公司的组件库设计的时候,对于同一组件的颜色设置基本有三种:正常色、较深色(常用于hover效果)、较浅色(常用于边框),所以第一步是可以根据正常色,获取深浅色。

3.1.1、获取 HSL 色值

深浅色的获取,基本是通过设置 HSL 颜色的亮度实现,所以第一步是将任意格式的色值,转换为 HSL 格式。

3.1.1.1、将 RGB 颜色转为 HSL

因可能会存入 RGB 颜色,所以需要将 RGB 转为 HSL 。下面是代码:

// rgb to hsl
function rgbToHsl (color) {
  let aColor = color.replace(/(?:\(|\)|rgb|RGB)*/g, '').split(',')
  const _R = aColor[0] / 255
  const _G = aColor[1] / 255
  const _B = aColor[2] / 255
  const Cmax = Math.max(_R, _G, _B)
  const Cmin = Math.min(_R, _G, _B)
  const V = Cmax - Cmin

  let H = 0
  if (V === 0) {
    H = 0
  } else if (Cmax === _R) {
    H = 60 * (((_G - _B) / V) % 6)
  } else if (Cmax === _G) {
    H = 60 * ((_B - _R) / V + 2)
  } else if (Cmax === _B) {
    H = 60 * ((_R - _G) / V + 4)
  }

  H = Math.floor(backCycle(H, 360))
  const L = numberFixed((Cmax + Cmin) / 2)
  const S = V === 0 ? 0 : numberFixed(V / (1 - Math.abs(2 * L - 1)))
  return `hsl(${H},${numberFixed(100 * S)}%,${numberFixed(100 * L)}%)`
}


function numberFixed (num = 0, count = 3) {
  const power = Math.pow(10, count)
  return Math.floor(num * power) / power
}


function backCycle (num, cycle) {
  let index = num % cycle
  if (index < 0) {
    index += cycle
  }
  return index
}
3.1.1.2、将 HEX 颜色转为 HSL

因可能会存入 HEX 颜色,所以需要将 HEX 转为 HSL 。具体步骤是,先将 HEX 转为 RGB,再将 RGB 转为 HSL

// hex 转 rgb
function hexToRgb (color) {
  if (color.length === 4) {
    let sColorNew = '#'
    for (let i = 1; i < 4; i += 1) {
      sColorNew += color.slice(i, i + 1).concat(color.slice(i, i + 1))
    }
    color = sColorNew
  }
  // 处理六位的颜色值
  let sColorChange = []
  for (let i = 1; i < 7; i += 2) {
    sColorChange.push(parseInt('0x' + color.slice(i, i + 2)))
  }
  return 'RGB(' + sColorChange.join(',') + ')'
}


RGB 转 HSL 见上部分。
3.1.2、HSL色值调亮度

HSL 调亮度,主要是改最后一位数字的值,代码如下:

// 获取更浅或者更暗的颜色,color为主色;amt为正数时获得浅色,为负数时获得深色。
function lightenDarkenColor (color, amt) {
  let hColor = colorToHsl(color).replace(/(?:\(|\)|hsl|HSL)*/g, '').split(',')
  let H = hColor[0]
  let S = getPercentNumber(hColor[1])
  let L = getPercentNumber(hColor[2]) + amt / 100
  L = L > 1 ? 0.98 : L
  return `hsl(${H},${numberFixed(100 * S)}%,${numberFixed(100 * L)}%)`
}


// 获取百分数的数值
function getPercentNumber (numberStr) {
  numberStr += ''
  if (numberStr.indexOf('%') > -1) {
    return parseFloat(numberStr) / 100
  } else {
    return parseFloat(numberStr)
  }
}

3.2、生成 style 代码

根据上面生成的深浅色,拼接 style 代码,对组件库和项目的色值进行覆盖,需要注意组件库的代码和项目中的代码要分开。

代码生成示例如下:

// 顶部导航的换肤 class
export let headerSkin = ({ skinColor, skinColorDarken, skinColorLighten } = {}) => {
  return `
        #skin .a-n-menu-primary ,
        #skin .a-n-menu-submenu .a-n-menu-submenu .a-n-menu .a-n-menu-item-active:not(.a-n-menu-submenu):before{
            background: ${skinColor};
        }

        #skin .a-n-menu-horizontal .a-n-menu-submenu:hover,
        #skin .a-n-menu-horizontal .a-n-menu-submenu:active,
        #skin .a-n-menu-horizontal .a-n-menu-item:hover,
        #skin .a-n-menu-horizontal .a-n-menu-item-active {
            background: ${skinColorDarken};
        }

        #skin .a-n-menu-vertical .a-n-menu-submenu .a-n-menu-item-selected>span.slotItem{
            color: ${skinColor};
        }

        #skin .a-n-menu-submenu {
            .a-select-dropdown {
                .a-n-menu-item:hover{
                    background: ${skinColorLighten}
                }
            }
        }
        #skin .aw-header .icon-container-right:hover,
        #skin .aw-header .icon-container:hover {
            background: ${skinColorDarken};
        }

    `
}

3.3、将 style 放到 head 中,完成主题色的替换

let css = 上面生成的style代码。
var head = document.getElementsByTagName('head')[0]
skinStyle = document.createElement('style')
skinStyle.innerHTML = css
head.appendChild(skinStyle)
相关推荐
m0_7482402526 分钟前
前端如何检测用户登录状态是否过期
前端
black^sugar27 分钟前
纯前端实现更新检测
开发语言·前端·javascript
寻找沙漠的人1 小时前
前端知识补充—CSS
前端·css
GISer_Jing1 小时前
2025前端面试热门题目——计算机网络篇
前端·计算机网络·面试
m0_748245521 小时前
吉利前端、AI面试
前端·面试·职场和发展
理想不理想v2 小时前
webpack最基础的配置
前端·webpack·node.js
pubuzhixing2 小时前
开源白板新方案:Plait 同时支持 Angular 和 React 啦!
前端·开源·github
2401_857600952 小时前
SSM 与 Vue 共筑电脑测评系统:精准洞察电脑世界
前端·javascript·vue.js
2401_857600952 小时前
数字时代的医疗挂号变革:SSM+Vue 系统设计与实现之道
前端·javascript·vue.js
GDAL2 小时前
vue入门教程:组件透传 Attributes
前端·javascript·vue.js