tiantingrui / daily-harvest

记录每日收获
MIT License
2 stars 0 forks source link

主题替换 #48

Open tiantingrui opened 1 year ago

tiantingrui commented 1 year ago

主题替换原理

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

tiantingrui commented 1 year ago

tailwind DarkMode 原理

Dark Mode: https://tailwindcss.com/docs/dark-mode

  1. 给对应DOM 增加 dark:
  2. 给 html 增加 dark

配置 tailwind.config.cjs

module.exports = {
  // 手动切换暗黑模式
  darkMode: 'class',
}
tiantingrui commented 1 year ago

DarkMode 在复杂业务中的实现逻辑

  1. 监听主题的切换行为 (onMenuItemChange)
  2. 根据当前行为保存需要展示主题(themeType) 到 pinia 中
  3. 根据 pinia 中的主题,展示header-theme下的icon显示图标 (computed)
  4. 根据 pinia 中的主题,修改 html 的 class (重点)
  5. 注意 缓存 theme (persist)
import { defineStore } from 'pinia'
import { THEME_LIGHT } from '@/constants'

export const useThemeStore = defineStore('theme', {
  state: () => ({
    theme: THEME_LIGHT // 初始值
  }),
  getters: {
    themeType: (state) => state.theme
  },
  actions: {
    changeTheme(newTheme) {
      this.theme = newTheme
    }
  },
  persist: {
    storage: sessionStorage,
    paths: ['theme']
  }
})
// header-theme.vue
<script setup>
import { THEME_LIGHT, THEME_DARK, THEME_SYSTEM } from '@/constants'
import { useThemeStore } from '@/store'
import { computed } from 'vue'

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 themeStore = useThemeStore()

/**
 * 展示图标
 */
const svgIcon = computed(() => {
  const findTheme = themeArr.find((theme) => {
    return theme.type === themeStore.themeType
  })
  return findTheme?.icon || themeArr[0].icon
})

/**
 * 切换主题
 * @param {*} item
 */
const onChangeTheme = (item) => {
  themeStore.changeTheme(item.type)
}
</script>

修改 html 的 class

// src/utils/theme.js
/**
 * 根据 pinia中保存的当前主题,修改 html的class
 */
import { watch } from 'vue'
import { useThemeStore } from '@/store/theme'
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 = () => {
    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 () => {
  // 1. 当主题发生改变时,或者当进入系统时,可以进行 html class的配置
  const themeStore = useThemeStore()
  watch(() => themeStore.theme, changeTheme, {
    // 初始执行一次
    immediate: true
  })
}
tiantingrui commented 1 year ago

跟随系统的主题变更

重点: 监听系统的主题变化

https://developer.mozilla.org/zh-CN/docs/Web/API/Window/matchMedia https://developer.mozilla.org/zh-CN/docs/Web/CSS/@media/prefers-color-scheme

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

该方法返回一个 MediaQueryList 对象:

  1. 该对象存在一个 change事件,可以监听主题发生变更的行为
  2. 同时存在一个 matches 属性,该属性为 boolean
    1. true:深色主题
    2. false: 浅色主题

代码实现 /src/utils/theme.js (上条 comment )

最后在 main.js 中引入 : 一定记住 app.use(pinia) 在 useTheme() 之前调用,以防拿不到 pinia 创建的实例。

// main.js

import useTheme from './utils/theme'

const pinia = createPinia()
pinia.use(piniaPluginPersistedstate)

const app = createApp(App)
app.use(router)
app.use(pinia)

// 初始化主题
useTheme()

app.use(mLibs)
app.mount('#app')