方案五:css变量+兼容IE

1,126 阅读4分钟

 这种方式是原生css+css变量的换肤方案,且在不支持css变量的浏览器上也可以换肤。

方案实现思路:

  1. 可以维护多个css文件(也只能写在css文件中),在index.html中引入
  2. 维护多个主题css变量文件
  3. 支持css变量的浏览器:页面切换主题-引用不同css变量文件 

       不支持css变量:结合css样式文件与css变量文件生成一个新的替换了css变量的样式文件

demo是基于vue写的。具体实现:

themeUtils.js:

import axios from 'axios' //用于读取css文件内容

//支持css变量类class SupTheme {  /**   * 不同的主题加载不同的css变量文件   */  constructor() {    this.link = null    this._curVarMap = {}  }  switchThemeByUrl(url) {    this._addLink(url)  }  _addLink(href) {    if (this.link) {      this.link.href = href      return    }    this.link = document.createElement('link')    this.link.id = 'themeStyle'    this.link.rel = 'stylesheet'    this.link.href = href    document.head.appendChild(this.link)  }  _removeLink() {    if (this.link) {      document.head.removeChild(this.link)      this.link = null    }  }}

//浏览器不支持css变量类
class NoSupTheme {  //大概思路  // 1.main.css中用css变量存着各个元素的css属性  // 2.切换主题时候,读取对应的css变量文件,将变量键值建立map  // 3.将不支持的css变量替换为map中对应的色值  /**   * 代码的主要逻辑:   * 1. 读取出页面引用的关于主题的css文件   * 2. 根据主题信息,加载不同的关于主题色值的css变量文件。   * 之后通过正则读取所有:root中定义的变量。/:root[^\}]*\{[^\}]*\}/gm   * 然后依据正则拿出每个css变量和与之对应的色值信息/\s\S*(--[^;\}]*)(;)/gs   * 最后形成一个css变量名称与css色值的map对象   * 3. 根据变量文件切割成不同的变量名称-css色值的map对象   * 4. 循环这个map对象,key为css文件中对应的css变量,value为对应的css色值。
        每遍历一个key都通过正则替换变量为对应的css色值var\\(${name}\\)   * 5. 最后将替换之后的css样式重新加载到文档中   */  constructor() {    this._style = null    this._init()  }  async switchThemeByUrl(url) {    let data = (await axios.get(url)).data    const newStyle = await this._getNewStyle(this._allStyle, data)    this._addStyle(newStyle)  }  async _init() {    const allStyle = await this._getAllStyle()    const newStyle = await this._getNewStyle(allStyle)    this._addStyle(newStyle)    this._allStyle = allStyle.replace(/:root[^\}]*\{[^\}]*\}/gm, '')  }  async _getNewStyle(styleStr, data2) {    let result = styleStr    let rootsStr = data2 || styleStr    const roots = rootsStr.match(/:root[^\}]*\{[^\}]*\}/g) || []    for (const root of roots) {      result = this._replaceVar(result, await this._getThemeMap(root))    }    return result  }  async _getThemeMap(data) {    let styleItems = data.match(/\s\S*(--[^;\}]*)(;)/gs)    const result = {}    if (!data) {      return result    }    for (const styleItem of styleItems) {      const items = styleItem.split(':')      result[items[0].replace(/\s*/g, '')] = items[1].replace(/[\s|;]/g, '')    }    return result  }  _replaceVar(text, themeMap) {    let result = text    for (const name in themeMap) {      const pattern = `var\\(${name}\\)`      result = result        .replace(new RegExp(pattern, 'g'), themeMap[name])    }    return result  }  async _getAllStyle() {    //获取元素样式文件内容    let result = ''    const links = document.querySelectorAll("[themeflag='theme']")    for (let index = links.length - 1; index >= 0; index--) {      const link = links[index]      result = (await axios.get(link.href)).data + result      link.parentElement.removeChild(link);    }    return result  }  _addStyle(str) {    if (!this._style) {      this._style = document.createElement('style')      document.head.appendChild(this._style)    }    this._style.innerHTML = str  }}
//切换主题的类
class ThemeUtil {  constructor() {    this.isSupported =      window.CSS && window.CSS.supports && window.CSS.supports('--a', 0)    if (this.isSupported) {     this.theme = new SupTheme();    } else {    this.theme = new NoSupTheme()    }  }  switchThemeByUrl(url) {    this.theme.switchThemeByUrl(url)  }}export const themeUtil = new ThemeUtil()

css文件:

/** * 新建css文件,放在public中 * main.css--页面元素的css样式。 * depp.css--深色主题css变量文件 * light.css--浅色主题css变量文件 */
//main.css:需要换肤的元素属性用css变量控制
.mainBtn{   background-color: var(--bg-white1) !important;}
.primaryBtn{ 
  background-color: var(--bg-white2) !important;
}//light.css
:root {  --bg-white4: #688b79;  --bg-white1: lightblue;  --bg-white2: #d5afaf;  --bg-white3: rgba(203, 149, 178, 0.73);}
//deep.css
:root {  --bg-white4: #688b79;  --bg-white1: black;  --bg-white2: #d5afaf;  --bg-white3: rgba(203, 149, 178, 0.73);}

index.html:

<link rel="stylesheet" themeflag="theme" href="./theme/main.css">

页面切换主题:

vue:
<script>
import {themeUtil} from "./themeUtil.js";
methods:{ 
  changeTheme(theme){ //theme:deep/light
    themeUtil.switchThemeByUrl(`./theme/${theme}.css`)  }
}
</script>

再说下换肤的页面逻辑:

  1. 首先在index.html引入控制元素样式的css文件
  2. 换肤的触发事件,changeTheme,调用 switchThemeByUrl方法,走到themeUtils.js中
  3. 不支持css变量的换肤实现,其实就是两点

      a. 读取css样式文件内容

      b. 读取css变量文件,形成一个map,类似于    

{ 
    --bg-white1: "lightblue",
    --bg-white2: "#d5afaf",
    --bg-white3: "rgba(203,149,178,0.73)",
    --bg-white4: "#688b79"
}

       c. 把读到的css样式文件内容中的var( --bg-white1)替换为 lightblue

       d. 把替换完成后的内容以style的方式插入到dom中

    这种实现的方式,优点是能避免硬编码css样式文件,只是通过不同的css变量就可以控制不同主题下的色值。而且还能够兼容ie。

但是缺点也很明显:

首先就是每次换肤,都需要读取对应主题的css变量,然后替换css变量的值,一旦页面的样式非常多,换肤的成本也会随之增大。

再就是,为了避免每次换肤都去读取css文件,把内容存了下来,这样一旦css文件较大,内容很多,很容易就内容泄漏。而且demo中还是一个css文件,如果三四个css样式文件,内存泄漏的风险会随之增高。

最后就是因为要将换肤的元素的css抽离出来成单独的css,在后期修改样式的时候还得从css文件中找,还会涉及到相互之间css权重问题,子组件的css样式给覆盖掉的问题

不过,总归是一种兼容方案,小记一下