思路:
前端主题切换,其实就是切换 css
而且,css 中变换最大的,就是颜色相关的属性,图片,icon....
1、css 变量 + css 样式【data-set [data-name='dark']】class='dark'
1.primary{
2
3 --vt-c-bg:#fff;
4}
5
6.dark{
7 --vt-c-bg:#000
8}
9
10可以使用js,点击某个按钮之后,切换根元素的css样式,样式切换之后,意味着css变量的值改变了
11.layer{
12 background-color:var(--vt-c-bg);
13}css 变量,是当我们点击某个按钮的时候做切换,可能如果变换比较大内容较多,会出现白屏的情况
2、css 预处理器(scss,less,styuls...) + css 样式 [data-set]
就是使用 css 预处理器强大的预编译功能,简单来说,就是把 css 先写好
1html.primary .layer{
2 color:#fff
3}
4
5html.dark .layer{
6 color:#000
7}
8html.primary .container{}
9
10html.dark .container{}
11
12html.primary .link:hover{}
13html.dark .link:hover{}css 预编译器,一开始,把所有的主题样式涉及到的内容全部写好,当切换的时候,就直接切换了
这种的方式,可能牺牲的是,一开始的白屏时间
1、安装 pinia 和持久化插件
1npm install pinia pinia-plugin-persistedstate2、创建 pinia
1//stores/index.ts
2import { createPinia } from "pinia";
3import piniaPluginPersistedstate from "pinia-plugin-persistedstate";
4
5// pinia persist
6const pinia = createPinia();
7pinia.use(piniaPluginPersistedstate);
8
9export default pinia;3、main.ts 中引用
1import { createApp } from "vue";
2import "./style.css";
3import App from "./App.vue";
4// pinia store
5import pinia from "@/stores";
6
7createApp(App).use(pinia).mount("#app");4、设置 pinia 持久化配置
1// config/piniaPersist.ts
2import { PersistedStateOptions } from "pinia-plugin-persistedstate";
3
4/**
5 * @description pinia 持久化参数配置
6 * @param {String} key 存储到持久化的 name
7 * @param {Array} paths 需要持久化的 state name
8 * @return persist
9 * */
10const piniaPersistConfig = (key: string, paths?: string[]) => {
11 const persist: PersistedStateOptions = {
12 key,
13 storage: localStorage,
14 // storage: sessionStorage,
15 paths,
16 };
17 return persist;
18};
19
20export default piniaPersistConfig;5、创建 global.ts
1import { defineStore } from "pinia";
2import piniaPersistConfig from "@/config/piniaPersist";
3import { GlobalState, ObjToKeyValArray } from "@/stores/interface";
4
5export const useGlobalStore = defineStore({
6 id: "dy-global",
7 // 修改默认值之后,需清除 localStorage 数据
8 state: (): GlobalState => ({
9 // 深色模式
10 isDark: false,
11 }),
12 persist: piniaPersistConfig("dy-global"),
13});6、设置 actions 统一的的函数
1import { defineStore } from "pinia";
2import piniaPersistConfig from "@/config/piniaPersist";
3import { GlobalState,ObjToKeyValArray } from "@/stores/interface";
4
5export const useGlobalStore = defineStore({
6 id: "dy-global",
7 // 修改默认值之后,需清除 localStorage 数据
8 state: ():GlobalState => ({
9 // 深色模式
10 isDark: false,
11 }),
12+ actions: {
13+ // Set GlobalState
14+ setGlobalState(...args: ObjToKeyValArray<GlobalState>) {
15+ this.$patch({ [args[0]]: args[1] });
16+ }
17+ },
18 persist: piniaPersistConfig("dy-global")
19});7、setGlobalState 函数参数的设定
1export interface GlobalState {
2 isDark: boolean;
3}
4export type ObjToKeyValArray<T> = {
5 [K in keyof T]: [K, T[K]];
6}[keyof T];8、修改 useTheme
1import { useGlobalStore } from "@/stores/modules/global";
2import { storeToRefs } from "pinia";
3
4export const useTheme = () => {
5
6 const globalStore = useGlobalStore();
7 const { isDark } = storeToRefs(globalStore);
8
9 const switchTheme = (init?: Boolean) => {
10 if (!init) {
11 globalStore.setGlobalState("isDark", !isDark.value);
12 }
13 const html = document.documentElement as HTMLElement;
14 console.log(isDark.value)
15 if (isDark.value) html.setAttribute("class", "dark");
16 else html.setAttribute("class", "primary");
17 }
18
19 const initTheme = () => {
20 switchTheme(true);
21 };
22
23
24 return {
25 switchTheme,
26 initTheme
27 }
28}9、界面调用
1import { useTheme } from "@/hooks/useTheme";
2const { initTheme, switchTheme } = useTheme();
3
4initTheme();
5
6const modeSwitch = () => {
7 // const theme = document.documentElement.className
8 // document.documentElement.className = theme === 'primary' ? 'dark' : 'primary'
9 switchTheme();
10};首先,使用媒体查询,可以实现跟随系统主题的效果,其实只有light和dark两种颜色
1body{
2 margin: 0;
3 padding: 0;
4}
5
6.container{
7 position: fixed;
8 width: 100%;
9 height: 100%;
10 overflow: auto;
11 transition:color .5s, background-color .5s;
12 color:#000;
13 background-color:#fff;
14}
15.layout{
16 display: flex;
17 justify-content: center;
18}
19
20.layer{
21 width: 300px;
22 height: 300px;
23 padding: 8px;
24 margin: 10px;
25 border:1px solid #000;
26}
27@media (prefers-color-scheme: dark) {
28 .container{
29 color:#fff;
30 background-color:#000;
31 }
32 .layer{
33 border:1px solid #fff;
34 }
35}我们可以通过api - window.matchMedia,来获取当前系统主题是深色还是浅色
1window.matchMedia("(prefers-color-scheme: dark)");有了这个 API,我们完全就没必要修改之前的 css 代码,直接 js 判断
1const media = window.matchMedia("(prefers-color-scheme: dark)");
2
3const followSystem = () => {
4 if (media.matches) {
5 document.documentElement.className = "dark";
6 } else {
7 document.documentElement.className = "primary";
8 }
9};
10
11followSystem();
12
13media.addEventListener("change", followSystem);直接在 vue3 项目中处理,添加界面系统主题图标,点击激活图标,表示激活跟随系统主题
1、添加图标
1<button>
2 <svg
3 xmlns="http://www.w3.org/2000/svg"
4 viewBox="0 0 16 16"
5 fill="none"
6 role="img"
7 >
8 <circle cx="8" cy="8" r="7.25" stroke="#5B5B66" stroke-width="1.5" />
9 <mask
10 id="a"
11 style="mask-type:alpha"
12 maskUnits="userSpaceOnUse"
13 x="0"
14 y="0"
15 width="16"
16 height="16"
17 >
18 <circle
19 cx="8"
20 cy="8"
21 r="7.25"
22 fill="#5B5B66"
23 stroke="#5B5B66"
24 stroke-width="1.5"
25 />
26 </mask>
27 <g mask="url(#a)">
28 <path fill="#5B5B66" d="M0 0h8v16H0z" />
29 </g>
30 </svg>
31</button>
32
33css .os-default{ background-color: transparent; border: none; padding: 0; color:
34var(--main-color); display: flex; justify-content: center; align-items: center;
35width: 20px; height: 20px; } .os-default-active{ background-color: transparent;
36border: 1px dotted var(--main-color); padding: 0px; color: var(--main-color);
37display: flex; justify-content: center; align-items: center; width: 20px;
38height: 20px; }2、仓库添加属性
1export const useGlobalStore = defineStore({
2 id: "dy-global",
3 state: () => ({
4 // 深色模式
5 isDark: false,
6+ // 系统颜色是否被激活
7+ osThemeActive: false
8 }),
9 getters: {},
10 actions: {
11 // Set GlobalState
12 setGlobalState(...args: ObjToKeyValArray<GlobalState>) {
13 this.$patch({ [args[0]]: args[1] });
14 }
15 },
16 persist: piniaPersistConfig("dy-global")
17});添加了属性,就必须修改 GlobalState 的 ts 配置
1export interface GlobalState {
2 isDark: boolean;
3+ osThemeActive: boolean;
4}
5export type ObjToKeyValArray<T> = {
6 [K in keyof T]: [K, T[K]];
7}[keyof T];3、点击修改 css 样式
1import { useTheme } from '@/hooks/useTheme'
2import { useGlobalStore } from "@/stores/modules/global";
3
4const globalStore = useGlobalStore();
5
6const {initTheme,switchTheme,activeOSTheme} = useTheme();
7
8initTheme();
9
10const modeSwitch = () => {
11 switchTheme()
12}
13
14const osThemeSwitch = () => {
15 activeOSTheme();
16}
17
18//html
19<button :class="globalStore.osThemeActive ? 'os-default-active' : 'os-default'">
20 <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="none" role="img" @click="osThemeSwitch">
21 ......省略
22 </svg>
23</button>4、修改 useTheme.ts
1import { useGlobalStore } from "@/stores/modules/global";
2import { storeToRefs } from "pinia";
3import { watchEffect } from "vue";
4
5const media = window.matchMedia('(prefers-color-scheme: dark)');
6
7export const useTheme = () => {
8 const globalStore = useGlobalStore();
9 const { isDark,osThemeActive } = storeToRefs(globalStore);
10
11 const switchTheme = (init?: Boolean) => {
12 //init参数为true时,表示是初始化页面或者直接跟随系统主题
13 //这个时候不需要手动切换明暗主题,也不需要将跟随系统主题设置为false
14 if (!init) {
15 globalStore.setGlobalState("isDark", !isDark.value);
16 //如果是直接点击切换明暗主题,那么跟随系统主题激活切换为false
17 globalStore.setGlobalState("osThemeActive", false);
18 }
19 const html = document.documentElement as HTMLElement;
20
21 if (isDark.value) html.setAttribute("class", "dark");
22 else html.setAttribute("class", "primary");
23 }
24
25
26 //点击激活需要改变osThemeActive的值
27 const activeOSTheme = () => {
28 globalStore.setGlobalState("osThemeActive", !osThemeActive.value);
29 followSystem();
30 }
31
32 const followSystem = () => {
33 const isOsDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
34 //跟随系统主题设置明暗
35 globalStore.setGlobalState("isDark", isOsDark);
36 switchTheme(true);
37 }
38
39 watchEffect(() => {
40 //系统主题切换,需要监听change事件
41 if (osThemeActive.value) {
42 media.addEventListener('change', followSystem);
43 }
44 else {
45 media.removeEventListener('change', followSystem);
46 }
47 })
48
49 const initTheme = () => {
50 switchTheme(true);
51 };
52
53 return {
54 switchTheme,
55 initTheme,
56 activeOSTheme
57 }
58}示例源码可按本文代码片段自行整理到项目中。