浅聊前端多主题
前言
在一个DOM上能有多种样式可切换,那便将“多主题”实现一半了,接下来我们只需要滚雪球,可同时切换多个DOM的样式,直至可切换整个页面的样式呈现,至此,“多主题”落地成功!
修改界限
-
色彩多主题:只在颜色上做改变,不同主题在同一个DOM上只存在属性值的差异,并不存在属性数量的变化,针对此场景,我只需要围绕
css属性值动态修改
思考即可。 -
布局多主题:除了颜色的变化外,整个页面的布局都存在较大改变,甚至有业务模块数量上的变化,此时已经不仅仅是css的事情了,dom结构都在发生着改变。那么实现方案就需要在实现色彩多主题的基础上再做其他思考。此方案比较复杂,可能还牵扯上权限的设计,本文便不做赘述了。
色彩多主题
先上结论,围绕css变量
实现多主题是目前的主流,包括像 ant-design
、element-plus
、vite官网
...也都是基于css变量
实现的。大致步骤如下:
1. 在根节点上声明css变量,例如:
:root{
--btn-border-color: red
}
2.更改css变量值,例如:
1.通过在根节点以行内样式的形式改变
document.documentelement.style.setProperty('--my-color', 'green')
2.也可以通过动态添加style
标签来覆盖
const style = document.createElement('style')
style.innerText = ":root{ --my-color: red }"
document.head.appendChild(style)
3.利用docuemnt.styleSheets
直接修改样式表
该方法是直接向样式表中增加/修改css变量,它的样式源码不会体现出来,如下图
const styles = document.createElement('style')
styles.setAttribute('id','theme')
document.head.appendChild(styles)
const styleSheet3 = Array.from(document.styleSheets).find(item => item.ownerNode.id == 'theme')
styleSheet3.insertRule(':root { --my-color:yellow }',styleSheet3.cssRules.length)
4.也可以通过改变link
标签的href
属性值,来拉取不同的样式文件,完成样式替换
5.利用vue3
中的新特性
<template>
<div class="sub">测试</div>
<button @click="colorChange">change</button>
</template>
<script setup>
import { ref } from 'vue'
const color = ref('red')
function colorChange(){
color.value = "yellow"
}
</script>
<style>
.sub{
color: v-bind(color)
}
</style>
方法不仅限以上几种,可根据自身项目特点进行选择研究。
非css变量方案
在vue2
时代(多主题本身和vue版本没关系,这里只是将其作为一个时间节点来看待) ,像element-ui
、ant-design
等诸多ui组件库,都还没有采用css变量的方案,基本都是用的scss
或者less
(虽然现在也是用的它们,但是现在都会提供一个css变量文件,来供使用者能在运行时做到css变量值覆盖),但这对我们来说,他们都是停留在编译前的阶段,而我们的动态换肤则是在编译后的运行时阶段,此时当初的less
变量、scss
变量早已被编译解析为了css
文件,那么我们想全局替换一些样式值时,便没有那么方便了。
例如vue-element-admin
中的多主题实现
ThemePicker.vue
async theme(val) {
const oldVal = this.chalk ? this.theme : ORIGINAL_THEME
if (typeof val !== 'string') return
const themeCluster = this.getThemeCluster(val.replace('#', ''))
const originalCluster = this.getThemeCluster(oldVal.replace('#', ''))
const getHandler = (variable, id) => {
return () => {
let styleTag = document.getElementById(id)
styleTag = document.createElement('style')
styleTag.setAttribute('id', id)
document.head.appendChild(styleTag)
styleTag.innerText = newStyle
}
}
updateStyle(style, oldCluster, newCluster) {
let newStyle = style
oldCluster.forEach((color, index) => {
newStyle = newStyle.replace(new RegExp(color, 'ig'), newCluster[index])
})
return newStyle
}
const url = `https://unpkg.com/element-ui@${version}/lib/theme-chalk/index.css`
await this.getCSSString(url, 'chalk')
const chalkHandler = getHandler('chalk', 'chalk-style')
chalkHandler()
}
上述代码中做了删减,它的做法是:
1.将element-ui中关于主题色的css源码以文本的形式请求到(unpkg.com/element-ui@…)
2.#409EFF
是elemnt-ui默认的主题色值,那么接下来就是使用字符串的replace
方法全局将原默认色值改为新设置的值
3.创建style
标签,将修改后的文本css代码插入到末尾
4.然后将element-ui默认的主题色就可以覆盖掉了,如图
所以其实无论是提前写好多套class类样式,然后通过切换dom的class类名;还是上面vue-element-admin这种实现方式。本质上都是在变相操控dom生效的css代码完成的。
除了多主题css样式切换的实现外,在思考多主题方案时还应注意以下问题
本地存储,持久化
主题选择这种用户偏好问题是需要持久化的,不可能用户刚设置完主题,刷新下页面就恢复初始状态了,存储可以选择sessionStorage
、localStorage
、Cookie
、indexDB
、服务端
。其中Cookie
就不推荐了,其余可根据自身场景选择,客户端存储的好处是不需要用户登录就可显现;服务端好处是可跨浏览器,跨客户端显现,用户换电脑后依然可以保持偏好设置。
同步客户端电脑偏好设置
现在很多操作系统都支持白天、黑夜主题,那么让我们的网页能跟随系统主题变化也至关重要,比如vite
、vue3
、githup
官网都已经支持了,最捞的不是你的网页没有实现多主题,而是明明实现了多主题却还需要用户手动切换。试想一下,当你切换了系统主题为黑夜模式后,网上冲浪,正十分安逸时,突然进入一个页面,电脑屏幕像是被扔了个闪光弹,企图闪瞎你的24K钛合金狗眼,然后缓过神后,看到网页上有个切换黑夜的按钮,此时这个按钮显的多么嘲讽(比如Ant Design Vue)。
js
判断系统主题:
window.matchMedia("(prefers-color-scheme: drak)").matches // true:暗色 false:亮色
css
判断系统主题
@media (prefers-color-scheme: dark) {
//...
}
@media (prefers-color-scheme: light) {
//...
}
// 或者利用color-scheme,但是color-scheme能力有限,可以作为兜底方案
结语
以上是对多主题的简单探讨,虽然其中涉及了许多细节,但距离真正的多主题实现方案还有许多差距。在实际项目中,我们需要根据具体情况进行量身定制,而不是盲目地在网上寻找一个所谓的"万金油"。同时,我们要保持持续的思考和探索,以便更好地应对未来可能出现的问题。
转载自:https://juejin.cn/post/7236248984335155255