前端主题色切换的终极解决方案

Hello大家好,我是日拱一卒的不浪,坚持从工作和生活中不断学习,提炼,沉淀,最终建立自己的强势领域!这是2024年输出的第6/100篇文章,欢迎志同道合的朋友一起学习交流;
公众号:攻城师不浪

绿泡泡:brown_7778

需求背景

最近在工作当中遇到一个需求,做一个后台管理系统,但是我们只做内容区,头部导航以及左侧菜单栏都不需要我们做,最后页面需要嵌套进兄弟部门的系统中,最终我们选择的用iframe的方案进行嵌套。

这时候需求就很明确了,因为切换主题的按钮在头部导航栏中,所以说我们是没法直接控制这个按钮的,只能通过iframe的src进行传参,然后我们根据这个参数去判断该用哪个主题色。

需求分析

看了网上比较火的几个方案介绍

juejin.cn/post/713459...

这里边介绍的大多是利用css的变量去解决,对我有一定的启发,但是我的需求里还需要在js中切换主题色,就比如echarts图表里的tooltip,也是要根据主题色进行变换的。

js 复制代码
 tooltip: {
    textStyle: {
      color: #fff // 如果是黑色主题,就需要浅灰色了
    },
    backgroundColor: #fff,
 }

用过echarts的都知道这个配置是在js里进行的,那我要怎么才能在js里统一获取到主题色的变量呢?

js中使用css变量

我猜,一定还有部分同学不知道如何在js中使用css变量,其实我也是最近才学会的技巧。

创建主题文件

这里我们结合我的项目使用less举例,css也是同样的道理。首先新建src/style/light.module.less,注意了,文件类型是module.less

css 复制代码
// 亮色主题
@color-primary: #0a8cfa;
:export {
  --color-primary: @color-primary;
}

全局引用

定义了less变量,我想要在项目的任何地方都能直接使用,以vite编译工具为例,在vite.config.js里配置

dart 复制代码
export default defineConfig({
  //...
  css: {
    preprocessorOptions: {
      less: {
        javascriptEnabled: true, // 允许在Less中使用js语法
        additionalData: `
          @import "${path.resolve(__dirname, 'src/style/light.module.less')}";
        `
      }
    }
  }
  //...
})

配置之后,就可以在项目的任何一个页面使用less变量

css 复制代码
// less代码
.box {
  color: @color-primary;
}

js中使用

还是结合项目,我使用的vue3,所以在script标签中使用

xml 复制代码
<script setup>
import cssVar from '@/style/light.module.less'

const primaryColor = cssVar['--color-primary'] // 输出#0a8cfa
</script>

感兴趣的同学可以尝试打印一下cssVar,它是一个对象,会把light.module.less里的:export内容以一个对象的形式全部输出。

主题色切换

创建暗色主题

我们已经创建了一个light.module.less了,接下来再创建一个暗色系:dark.module.less

css 复制代码
// 暗色主题
@color-primary: #000;
:export {
  --color-primary: @color-primary;
}

保持变量名一致,只是不同。

主题色文件按需加载

通过路由拿到主题参数参数dark,例如

js 复制代码
xxx?theme=dark

然后在router.beforeEach全局前置守卫钩子处进行判断,定义route文件,例如/src/route/route.js

js 复制代码
import { createRouter, createWebHashHistory } from 'vue-router'
import { createPinia } from 'pinia'
import { useGlobalStore } from '@/store/global'

const routes = [
  //...
]
// 创建路由
export const router = createRouter({
  // hash 模式。
  history: createWebHashHistory(),
  routes
})
export const pinia = createPinia()

// 设置html标签的style属性,将所有的主题色变量都添加到行内样式
const setStyle = (styles) => {
  if (styles) {
    for (let key in styles) {
      document.getElementsByTagName('html')[0].style.setProperty(`${key}`, styles[key])
    }
  }
}
// 全局前置守卫,进每个页面前都会触发该钩子
router.beforeEach(async (to, from, next) => {
  const { theme } = to.query
  const globalStore = useGlobalStore()
  // 根据路由参数判断需要加载的主题色文件
  await import(`@/style/${theme}.module.less`).then((module) => {
    // module.default就是主题色文件导出的所有颜色变量
    if (module.default) {
      // pinia创建的全局状态
      globalStore.setTheme(module.default, theme)
      setStyle(module.default)
    }
    next()
  })
})

export default routes

setStyle做的事情就是将所需的变量都塞进html的style标签里,如下:

塞进之后,我们就能够在css里直接使用主题色变量了

css 复制代码
.box {
  background: var(--color-primary)
}

也可以直接在html标签中使用:

html 复制代码
<div style="color: var(--color-primary)">测试模板使用</div>

再看下useGlobalStore做的事情,当路由参数发生变化时,及时切换全局状态,创建/src/store/global.js

js 复制代码
import { defineStore } from 'pinia'

export const useGlobalStore = defineStore('global', {
  state: () => {
    return {
      theme: {}, // 主题色配置
      themeName: 'light' // 主题色名称
    }
  },
  actions: {
    // 当路由参数发生变化时,及时切换全局状态
    setTheme(theme, themeName) {
      this.theme = theme
      this.themeName = themeName
    }
  }
})

js获取切换后的主题色变量

在js中,通过全局store获取更新后的主题色变量

js 复制代码
<script setup>
import { useGlobalStore } from '@/store/global'
const globalStore = useGlobalStore()
// 更新后的主题色,获取的是一个对象
const theme = globalStore.theme
</script>

echarts的主题色不更新

如果项目中用到echarts,并且tooltip也会随着主题色变换而更新,此时你会发现颜色并没有随着全局store的theme的变化而更新,如下:

js 复制代码
const chartOption = ref({})
const drawRateChart = () => {
  chartOption.value = {
    title: [
     //...
    ],
    tooltip: {
      trigger: 'item',
      textStyle: {
        color: globalStore.theme['--color-primary']
      },
      backgroundColor: globalStore.theme['--color-primary']
    },
    //...
  }
}

目前我的做法是监听全局store的themeName,当它发生改变时,重新执行一遍chartOption的赋值

js 复制代码
watch(
  () => globalStore.themeName,
  (newVal, oldVal) => {
    if (newVal !== oldVal) {
      drawRateChart()
    }
  }
)

如果有更好的方案,欢迎评论区交流。

总结

  1. 利用module.less可以导出变量的能力;
  2. 利用路由全局前置守卫beforeEach按需加载主题色文件;
  3. 根据路由参数更新html标签的行内主题色变量;
  4. 利用状态管理在js中获取更新后的主题色变量;
  5. echarts如何更换主题色;

好了,以上就是根据最近的奇葩业务需求,我所给出的方案,如果有更加优雅的方案,欢迎和我(brown_7778)探讨~

如果在成长的路上感到孤单,可以交个朋友:brown_7778,围观我的朋友圈,每天输出技术、科技、认知与感悟,让我们携手并进。

如果觉得文章对你有帮助,欢迎点赞``关注``转发,你的鼓励是支持我持续原创下去的动力~

相关推荐
Mintopia25 分钟前
像素的进化史诗:计算机图形学与屏幕的千年之恋
前端·javascript·计算机图形学
Mintopia28 分钟前
Three.js 中三角形到四边形的顶点变换:一场几何的华丽变身
前端·javascript·three.js
归于尽42 分钟前
async/await 从入门到精通,解锁异步编程的优雅密码
前端·javascript
陈随易43 分钟前
Kimi k2不行?一个小技巧,大幅提高一次成型的概率
前端·后端·程序员
猩猩程序员1 小时前
Rust 动态类型与类型反射详解
前端
杨进军1 小时前
React 实现节点删除
前端·react.js·前端框架
yanlele1 小时前
【实践篇】【01】我用做了一个插件, 点击复制, 获取当前文章为 Markdown 文档
前端·javascript·浏览器
爱编程的喵1 小时前
React useContext 深度解析:告别组件间通信的噩梦
前端·react.js
望获linux2 小时前
【实时Linux实战系列】多核同步与锁相(Clock Sync)技术
linux·前端·javascript·chrome·操作系统·嵌入式软件·软件