likes
comments
collection
share

浅聊前端多主题

作者站长头像
站长
· 阅读数 14

前言

在一个DOM上能有多种样式可切换,那便将“多主题”实现一半了,接下来我们只需要滚雪球,可同时切换多个DOM的样式,直至可切换整个页面的样式呈现,至此,“多主题”落地成功!

修改界限

  • 色彩多主题:只在颜色上做改变,不同主题在同一个DOM上只存在属性值的差异,并不存在属性数量的变化,针对此场景,我只需要围绕css属性值动态修改思考即可。

  • 布局多主题:除了颜色的变化外,整个页面的布局都存在较大改变,甚至有业务模块数量上的变化,此时已经不仅仅是css的事情了,dom结构都在发生着改变。那么实现方案就需要在实现色彩多主题的基础上再做其他思考。此方案比较复杂,可能还牵扯上权限的设计,本文便不做赘述了。

色彩多主题

先上结论,围绕css变量实现多主题是目前的主流,包括像 ant-designelement-plusvite官网...也都是基于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-uiant-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样式切换的实现外,在思考多主题方案时还应注意以下问题

本地存储,持久化

主题选择这种用户偏好问题是需要持久化的,不可能用户刚设置完主题,刷新下页面就恢复初始状态了,存储可以选择sessionStoragelocalStorageCookieindexDB服务端。其中Cookie就不推荐了,其余可根据自身场景选择,客户端存储的好处是不需要用户登录就可显现;服务端好处是可跨浏览器,跨客户端显现,用户换电脑后依然可以保持偏好设置。

同步客户端电脑偏好设置

现在很多操作系统都支持白天、黑夜主题,那么让我们的网页能跟随系统主题变化也至关重要,比如vitevue3githup官网都已经支持了,最捞的不是你的网页没有实现多主题,而是明明实现了多主题却还需要用户手动切换。试想一下,当你切换了系统主题为黑夜模式后,网上冲浪,正十分安逸时,突然进入一个页面,电脑屏幕像是被扔了个闪光弹,企图闪瞎你的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能力有限,可以作为兜底方案

结语

以上是对多主题的简单探讨,虽然其中涉及了许多细节,但距离真正的多主题实现方案还有许多差距。在实际项目中,我们需要根据具体情况进行量身定制,而不是盲目地在网上寻找一个所谓的"万金油"。同时,我们要保持持续的思考和探索,以便更好地应对未来可能出现的问题。