sass 实现多主题切换
新建文件以存储各主题对应的样式
// new-simulation.scss 文件内容如下:(此文件用于存储 new-simulation 主题特有的样式)
$ns-main-color: #ecf0f3;
$new-simulation: (
main-color: $ns-main-color,
inset-bg-color: #f0f0f0,
);
// primary.scss 文件内容如下:(此文件用于存储 primary.scss 主题特有的样式)
$ns-main-color: #505050;
$primary: (
main-color: $ns-main-color,
bg-color: #f0f0f0,
);
如上所示,每个主题都放在不一样的文件中,每新增一个主题就可以通过新建文件来实现
新建 theme.scss 文件用于存储与切换主题相关的函数
// theme.scss 文件内容如下:(此文件用于封装生成各类主题样式的函数,后续开发只需要使用 themeify 和 themed 包裹就可以自动匹配对应主题的样式)
// 引入各主题样式
@import './primary.scss';
@import './new-simulation.scss';
$themes:(
primary: $primary,
new-simulation: $new-simulation
);
$current-theme-map: 'primary';
@mixin themeify {
@each $theme-name, $theme-map in $themes {
$current-theme-map: $theme-map !global;
// 这里需要根据自己绑定 的主题样式名进行修改
// 本示例是通过在 body 上增加 data-theme 属性来区分当前是哪个主题
body[data-theme="#{$theme-name}"] & {
@content;
}
}
}
@function themed($key) {
@return map-get($current-theme-map, $key);
}
使用 themeify
使用示例:
.test {
padding: 0;
margin: 0
@include themeify {
color: themed('text-color');
}
}
// 上述代码编译后的结果如下所示:
body[data-theme=primary] .test {
color: #000000;
}
body[data-theme=new-simulation] .test {
color: #696868;
}
可以在
style/index.scss或 根据其它自定义文件 中 引入themeify.scss文件,并在vite.config.ts中进行配置,这样在项目的其他文件中可以不引入也能正常使用,如下所示:(本示例是 在style/index.scss文件中引入themeify.scss文件,并在vite.config.ts进行预处理)
// vite.config.ts 文件部分内容:
import { defineConfig } from 'vite'
export default defineConfig(({mode}) => {
return {
css: {
preprocessorOptions: {
scss: {
additionalData: '@import "@/styles/theme.scss"',
},
},
},
}
})
封装自定义 hook 以实现通过切换 body 属性来更改当前显示的样式
React :
useTheme.ts 文件内容:
import { StorageKey, ThemeKey } from "@/types/global"
/**
StorageKey 为自定义的enum, 目的只是为了统一管理 localStorage / sessionStorage 的 key 值, 其部分内容如下:
const enum StorageKey {
THEME = 'THEME',
}
ThemeKey 为自定义的enum, 目的只是为了统一管理主题名, 其部分内容如下所示:
const enum ThemeKey {
PRIMARY = 'primary',
NEWSIMULATION = 'new-simulation'
}
*/
import { useCallback, useEffect, useState } from "react"
// 多主题切换
let isInit: boolean = false
const useTheme = () => {
const [theme, setTheme] = useState<ThemeKey>(ThemeKey.NEWSIMULATION)
const toggleTheme = useCallback((theme: ThemeKey) => {
document.body.setAttribute('data-theme', theme)
setTheme(theme)
localStorage.setItem(StorageKey.THEME, theme)
}, [])
useEffect(() => {
if (!isInit) {
isInit = true
const theme = localStorage.getItem(StorageKey.THEME) as ThemeKey || ThemeKey.NEWSIMULATION
toggleTheme(theme)
}
}, [])
return { theme, toggleTheme }
}
export default useTheme
在 App.tsx 中使用并注册到 App 的上下文中以方便其他组件使用(如果是 vue, 可以挂载到 app 实例上)
// App.tsx 文件内容:
import { Suspense, useEffect, useState } from 'react';
import { useRoutes } from 'react-router-dom';
import routes from '@/router/index.tsx';
import useTheme from './hooks/useTheme';
import { AppProvider } from './AppContenx';
function App() {
const { theme, toggleTheme } = useTheme();
return (
<AppProvider value={{ theme, toggleTheme }}>
<div>
<Suspense fallback="">
<div className="main">{useRoutes(routes)}</div>
</Suspense>
</div>
</AppProvider>
);
}
export default App;
// ./AppContenx.ts 文件内容:
import { createContext } from "react";
import { StorageKey, ThemeKey } from "./types/global";
// 创建 app 上下文
export interface AppContextProps {
theme: ThemeKey;
toggleTheme: (theme: ThemeKey) => void;
}
const AppContext = createContext<AppContextProps>({} as AppContextProps);
export const AppProvider = AppContext.Provider;
export default AppContext;
后续其他页面中如果需要切换主题,可以直接使用 useContext 获取 toggleTheme 即可,如下所示:
import AppContext from '@/AppContenx'
const { toggleTheme } = useContext(AppContext)
vue3
useTheme.ts 文件内容:
import { onMounted, ref } from "vue"
// 多主题切换
let isInit: boolean = false
export enum ThemeKey {
PRIMARY = 'primary',
NEWSIMULATION = 'new-simulation'
}
export enum StorageKey {
THEME = 'THEME',
}
export const useTheme = () => {
const theme = ref<ThemeKey>(ThemeKey.NEWSIMULATION)
const toggleTheme = (_theme: ThemeKey) => {
document.body.setAttribute('data-theme', _theme)
theme.value = _theme
localStorage.setItem(StorageKey.THEME, _theme)
}
onMounted(() => {
if (!isInit) {
isInit = true
const _theme = localStorage.getItem(StorageKey.THEME) as ThemeKey || ThemeKey.NEWSIMULATION
toggleTheme(_theme)
}
})
return { theme, toggleTheme }
}
使用 useTheme 实现点击按钮切换主题功能
<template>
<div class="layout">
<button @click.prevent="changeTheme(ThemeKey.NEWSIMULATION)">newSimulation</button>
<button @click.prevent="changeTheme(ThemeKey.PRIMARY)">primary</button>
</div>
</template>
<script setup lang="ts">
import { ThemeKey, useTheme } from '@/hooks/useTheme';
const changeTheme = (type: ThemeKey) => {
toggleTheme(type)
}
</script>