vue深色模式浅色模式切换思路

其实现在的组件库支持浅色深色模式切换的很多,听说tailwindCss好像很方便,但是我还没用过,我用elemen-plus比较多,先记录一篇vueuse + elementplus 主题切换思路
首先 安装vueuse 和elementplus,这里就不做演示了
根目录创建composables,这里封装下vueuse我们需要用到的方法和参数
在composables目录下创建dark.ts和index.ts

// dark.ts
import { useDark, useToggle } from '@vueuse/core'

export const isDark = useDark()
export const toggleDark = useToggle(isDark)
// index.ts
export * from './dark'

然后写一下公共样式

//index.scss
// import dark theme
@use 'element-plus/theme-chalk/src/dark/css-vars.scss' as *;

// :root {
//   --ep-color-primary: red;
// }

body {
  font-family: Inter, system-ui, Avenir, 'Helvetica Neue', Helvetica, 'PingFang SC', 'Hiragino Sans GB',
  'Microsoft YaHei', '微软雅黑', Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  height: 100vh;
  margin: 0;
}

#app {
  width: 100vw;
  height: 100vh;
}

a {
  color: var(--ep-color-primary);
}

code {
  border-radius: 2px;
  padding: 2px 4px;
  background-color: var(--ep-color-primary-light-9);
  color: var(--ep-color-primary);
}

然后在main.ts中引入index.scss

import 'element-plus/dist/index.css'
import '@/styles/index.scss'                //注意一定得在element-plus/dist/index.css后引入

然后封装一个切换主题的组件

// ChangeTheme.vue
<script setup lang="ts">
import {nextTick, ref, watch} from 'vue'
import type {SwitchInstance} from 'element-plus'
import {useDark, useToggle} from "@vueuse/core";

defineOptions({inheritAttrs: false})
const isDark = useDark()
const darkMode = ref(isDark)
const switchRef = ref<SwitchInstance>()

watch(
    () => darkMode.value,
    () => {
      useToggle()
    }
)

const beforeChange = () => {
  return new Promise<boolean>((resolve) => {
    const isAppearanceTransition =
        document.startViewTransition &&
        !window.matchMedia('(prefers-reduced-motion: reduce)').matches
    if (!isAppearanceTransition) {
      resolve(true)
      return
    }

    const switchElement = switchRef.value?.$el
    const rect = switchElement.getBoundingClientRect()
    const x = rect.left + rect.width / 2
    const y = rect.top + rect.height / 2

    const endRadius = Math.hypot(
        Math.max(x, innerWidth - x),
        Math.max(y, innerHeight - y)
    )

    const ratioX = (100 * x) / innerWidth
    const ratioY = (100 * y) / innerHeight
    const referR = Math.hypot(innerWidth, innerHeight) / Math.SQRT2
    const ratioR = (100 * endRadius) / referR

    const transition = document.startViewTransition(async () => {
      resolve(true)
      await nextTick()
    })
    transition.ready.then(() => {
      const clipPath = [
        `circle(0% at ${ratioX}% ${ratioY}%)`,
        `circle(${ratioR}% at ${ratioX}% ${ratioY}%)`,
      ]
      document.documentElement.animate(
          {
            clipPath: isDark.value ? [...clipPath].reverse() : clipPath,
          },
          {
            duration: 400,
            easing: 'ease-in',
            pseudoElement: isDark.value
                ? '::view-transition-old(root)'
                : '::view-transition-new(root)',
          }
      )
    })
  })
}
</script>

<template>
  <el-switch
      ref="switchRef"
      v-model="darkMode"
      v-bind="$attrs"
      inline-prompt
      style="--el-switch-on-color: rgba(176,176,176,0.3); --el-switch-off-color: #409EFF"
      :before-change="beforeChange"
      active-icon="Moon"
      inactive-icon="Sunny"
  />
</template>

<style lang="scss" scoped>

:deep(.dark-icon) {
  border-radius: 50%;
  color: #cfd3dc;
  background-color: #141414;
}

:deep(.light-icon) {
  color: #606266;
}
</style>

大功告成

posted @ 2025-08-03 00:20  OvOGhostFace  阅读(28)  评论(0)    收藏  举报