主题替换解决方案-打造完善多主题

目录

01: 主题替换原理分析

02: TailWind DarkMode 原理

03: 为组件增加 Dark 适配

04: DarkMode 在复杂应用中的实现逻辑分析

05: DarkMode 在复杂应用中的实现

06: 跟随系统的主题变更

07: 总结


01: 主题替换原理分析

        主题替换原理:通过类名来控制对应的样式(主题),当类名发生变化时,即完成了主题替换。

02: TailWind DarkMode 原理

// tailwind.config.js

module.exports = {

    // 手动切换暗黑模式
    darkMode: 'class'

    ……
}
// 受影响的元素
<div class="bg-white dark:bg-zinc-800"></div>

// 设置主题
<html class="dark"></html>

03: 为组件增加 Dark 适配

        各个组件添加 " class='dark:bg-zinc-800 dark:hover:bg-zinc-300' " 。

04: DarkMode 在复杂应用中的实现逻辑分析

        1. 监听主题的切换行为。

        2. 根据行为,保存当前需要展示的主题到 vuex 中。

        3. 根据 vuex 中保存的当前主题,展示 header-theme 下的显示图标。

        4. 根据 vuex 中保存的当前主题,修改 html 的 class。

05: DarkMode 在复杂应用中的实现

- store
- - modules
- - - theme.js
- - getters.js
- - index.js
// src/store/modules/theme.js

import { THEME_LIGHT } from '@/constants'

export default {
    namespaced: true,
    state: () => ({
        // 当前主题模式
        themeType: THEME_LIGHT
    }),
    mutations: {
        changeThemeType(state, newTheme) {
            state.themeType = newTheme
        }
    }
}
// src/store/index.js

import theme from './modules/theme'

const store = createStore({
    ……
    modules: {
        theme,
    }
    ……
}
// src/store/getters.js

export default {
    themeType: (state) => state.theme.themeType
}
// src/views/layout/components/header/header-theme.vue

<script setup>
    import { useStore } from 'vuex'
    
    // 构建渲染数据源
    const themeArr = [
      {
        id: '0',
        type: THEME_LIGHT,
        icon: 'theme-light',
        name: '极简白'
      },
      {
        id: '1',
        type: THEME_DARK,
        icon: 'theme-dark',
        name: '极夜黑'
      },
      {
        id: '2',
        type: THEME_SYSTEM,
        icon: 'theme-system',
        name: '跟随系统'
      }
    ]

    const store = useStore()
    /**
     * menu 切换事件
     */
    const onItemClick = (themeItem) => {
        store.commit('theme/changeThemeType', themeItem.type)
    }

    /**
     * 控制图标展示
     */
    const svgIconName = computed(() => {
        const findTheme = themeArr.find((theme) => {
            return theme.type === store.getters.themeType
        }
        return findTheme?.icon || themeArr[0].type
    })
</script>
- utils
- - theme.js
// src/utils/theme.js

import { watch } from 'vue'
import store from '../store'

/**
 * 初始化主题
 */
export default () => {
    // 1. 当主题发生改变时,或者当进入系统时,可以进行 html class 的配置
    watch(
        () => store.getters.themeType,
        (val) => {
            // html 的 class
            let themeClassName = ''
            switch (val) {
                case THEME_LIGHT:
                    themeClassName = 'light'
                    break
                case THEME_DARK:
                    themeClassName = 'dark'
                    break
            // 修改 html 的 class
            document.querySelector('html').className = themeClassName
        },
        {
            // 初始执行一次
            immediate: true
        }
    )
}
// src/main.js

import useTheme from './utils/theme'

useTheme()

06: 跟随系统的主题变更

想要生成跟随系统的主题变更,那么我们就需要 **监听系统的主题变化**

想要做到这一点,可以利用 Window.matchMedia() 方法,该方法接受一个 mediaQueryString (媒体查询解析的字符串),该字符串我们可以传递 prefers-color-schema,即 window.matchMedia('(prefers-color-schema: dark)') 方法。

该方法可以返回一个 MediaQueryList 对象:

1. 该对象存在一个 change 事件,可以监听 主题发生变更 的行为。

2. 同时存在一个 matches 属性,该属性为 boolean 性的值:

        1. true:深色主题

        2. false:浅色主题

那么据此,可生成以下代码,在 src/utils/theme.js 中:

import store from '@/store'
import { watch } from 'vue'
import { THEME_LIGHT, THEME_DARK, THEME_SYSTEM } from '@/constants'

/**
 * 监听系统主题变更
 */
let matchMedia
const watchSystemThemeChange = () => {
  // 仅需初始化一次即可
  if (matchMedia) return
  matchMedia = window.matchMedia('(prefers-color-scheme: dark)')
  // 监听主题变更
  matchMedia.onchange = function () {
    changeTheme(THEME_SYSTEM)
  }
}

/**
 * 变更主题
 * @param {*} theme 主题的标记常量
 */
const changeTheme = (theme) => {
  // html 的 class
  let themeClassName = ''
  switch (theme) {
    case THEME_LIGHT:
      themeClassName = 'light'
      break
    case THEME_DARK:
      themeClassName = 'dark'
      break
    case THEME_SYSTEM:
      watchSystemThemeChange()
      themeClassName = matchMedia.matches ? 'dark' : 'light'
      break
  }
  // 修改 html 的 class
  document.querySelector('html').className = themeClassName
}

/**
 * 初始化主题
 */
export default () => {
  watch(() => store.getters.themeType, changeTheme, {
    // 初始执行一次
    immediate: true
  })
}

07: 总结

主要讲解了 主题替换的功能,包含以下 4 个主要方面:

1. 主题替换原理

2. tailwind 主题替换原理

3. 复杂应用中的实现方案

4. 跟随系统的主体变更

目前市面上很多的 组件库 也都包含了主题替换的功能,它们的实现原理其实也就是本文章中所讲解的方式。

看完本文章之后,应该也可以对主题替换这样的普适功能有更加深入的了解啦~ 

  • 21
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

chengbo_eva

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值