本文章是基于ElementUI theme-chalk-preview方法上的改进
效果展示
切换前:
切换后:
先来说一下为什么选择该方案吧,为什么有CSS变量替换等其他更优方法不用呢?
因为IE
!!!2021了,居然还要支持IE(我不李姐😐 )
言归正传,先来了解一下原方法的思路吧😆
以下步骤代码摘抄至源码
1、使用XMLHttpRequest请求服务端的CSS样式文件(经步骤 2 处理后,将替换后的全部样式保存在 originalStyle
中)
getFile (url, isBlob = false) {
return new Promise((resolve, reject) => {
const client = new XMLHttpRequest()
client.responseType = isBlob ? 'blob' : ''
client.onreadystatechange = () => {
if (client.readyState !== 4) {
return
}
if (client.status === 200) {
const urlArr = client.responseURL.split('/')
resolve({
data: client.response,
url: urlArr[urlArr.length - 1]
})
} else {
reject(new Error(client.statusText))
}
}
client.open('GET', url)
client.send()
})
},
getIndexStyle () {
this.getFile('//unpkg.com/element-ui/lib/theme-chalk/index.css')
.then(({ data }) => {
this.originalStyle = this.getStyleTemplate(data)
})
}
2、将获取到的样式中涉及到的颜色值替换成关键词
getStyleTemplate (data) {
const colorMap = {
'#3a8ee6': 'shade-1',
'#409eff': 'primary',
'#53a8ff': 'light-1',
'#66b1ff': 'light-2',
'#79bbff': 'light-3',
'#8cc5ff': 'light-4',
'#a0cfff': 'light-5',
'#b3d8ff': 'light-6',
'#c6e2ff': 'light-7',
'#d9ecff': 'light-8',
'#ecf5ff': 'light-9'
}
Object.keys(colorMap).forEach(key => {
const value = colorMap[key]
data = data.replace(new RegExp(key, 'ig'), value)
})
return data
}
替换前:
h {
color: #3a8ee6;
}
替换后
h {
color: shade-1;
}
3、根据用户选择的颜色,自动生成每个关键词对应的颜色值
4、将新生成的颜色值替换回对应的关键词处
5、新增style标签,将样式写入
if (this.originalStylesheetCount === document.styleSheets.length) {
const style = document.createElement('style')
style.innerText = cssText document.head.appendChild(style)
} else {
document.head.lastChild.innerText = cssText
}
新增style标签前,先判别是否曾添加过,若无,则新建style,将其添加至末尾。若有,则直接覆盖末尾子节点的内容。
至此我们就完成了自定义主题切换的全部功能
现在我们来思考如何优化他
1、开发模式下支持主题色替换
一般我们在做开发时,都是在本地运行代码,此时上述方式便失效了(ElementUI项目运行时无法以上述请求方式拿到样式文件),一旦项目大起来,编译打包,再映射,查看该功能是否生效,一来费时,二来出现问题也不好定位,因此我们能不能在开发模式下,也支持该功能呢?
问题的关键就在于,如何拿到页面上的CSS样式,此时我们就要借助他的帮忙 styleSheets
关注其中的 cssRules 属性(如下图所示)
诶,cssText 属性中不就存着我们所需的样式嘛?由此我们可以遍历页面上所有的 styleSheets ,然后将cssRules.cssText一行一行的拼凑起来,不就是一个完整的样式了吗?
for (let j = 0; j < document.styleSheets.length; j++) {
try {
for (let i = 0; i < document.styleSheets[j].cssRules.length; i++) {
try {
this.originalStyle = this.originalStyle.concat(document.styleSheets[j].cssRules[i].cssText);
} catch (error) {
continue;
};
}
} catch (error) {
continue;
};
}
2、支持 rgb 色值的更改
我们都知道色值有许多种表示方式,在 CSS 中最为常见的莫过于rgb 和 hex ,可上述的例子中仅支持了 hex 的修改,若是某些颜色需带有透明度(或者同种颜色)用了 rgb 的方式表示,就不能修改了。( hex 也可以表示透明度,只可惜 IE
依然不支持)
我们可以把 colorMap(不知道colorMap是什么的,赶紧往上翻!!!不认真!点名批评💢 )变为以下格式
const colorMap = {
'64,158,255': 'primary', // 64,158,255 是 #409eff 对应 rgb 值
'64, 158, 255': 'primary',
'#409eff': 'rgb(primary)'
}
ElementUI 颜色选择器,选择后传出的色值是 hex 格式的,因此我们的色值生成函数,需要进行 hex 到 rgb 的转换,具体函数这里就不贴了,网上很多。
3、删除指定的style标签
原方法是删除最后一个 style 标签,若最后一个 style 标签不是我们新增的呢?此时就会造成误删的情况。
if (document.querySelector("style[title='replaceStyle']")) {
try {
document.querySelector("style[title='replaceStyle']").remove();
} catch (error) {
document.querySelector("style[title='replaceStyle']").removeNode();
}
}
新增 style标签 时,附加一个 title ,每次删除时,只删除指定 title 的 style标签。
4、提升IE浏览器下的替换速度
当新增过 style 标签后,后续的每次替换,我们就将样式覆盖进去,但这速度在 IE 上,不敢恭维,速度慢得令人崩溃😩 ,那有没有什么方法可以提速呢?
实测发现,直接删除原新增的 style 标签,然后再重新添加 style 标签,速度居然比我们直接覆盖更快!!!但这还是远远不够,IE表示,还是很吃力🙉 。此时我们可以利用 Fragment ,让我们先简单了解一下他。
DocumentFragment 是 DOM 节点。不是主 DOM 树的一部分。常用于创建文档片段,将元素附加到文档片段,然后将文档片段附加到 DOM 树。在 DOM 树中,文档片段被其所有的子元素所代替。因为文档片段存在于内存中,并不在 DOM 树中,所以将子元素插入到文档片段时不会引起页面回流(对元素位置和几何上的计算)。因此,使用文档片段通常会带来更好的性能。
const dq = document.createDocumentFragment(); //新建fragment片段
const style = document.createElement('style'); //创建元素
style.title = 'replaceStyle'; //添加标题
style.innerHTML = cssText;
dq.appendChild(style); //元素插入至fragment片段中
document.head.appendChild(dq); //插入片段的所有子元素
处理过后,现在 IE 上的主题切换,也变得非常丝滑啦~
最后
本文到此结束,希望对你有所帮助。若有什么好的建议,可以多多交流呀。文中若有不正之处,欢迎指正。
前端萌新向各位大佬鞠躬了,谢谢啦 😘