前端开发人员面临着制作增强用户体验和适应用户偏好的用户界面的任务。带有 css
的 react
主要可用于创建多色可切换主题。为用户提供了在给定时间点在主题颜色之间切换以适合他们的偏好的特权。
介绍
在本文中,我们将讨论使用 react
、less
和其他依赖项构建多色主题的各种步骤和要求。目前市面上用的多的就两种方案:
- React-styled-components
- 全局维护两套主题色(假如只有
light
和dark
两种主题)以及全局的root
变量以供组件引用,配合自定义context
实现不同主题切换功能
个人不喜欢 styled-components
这种方法,样式、结构、脚本混在一起感觉有点别扭,感兴趣的可以自己去尝试,本文主要介绍第二种方法,并且配合 react
比较流行的组件库antd
进行开发。
前置知识
读者应该有 react
、less
和他们选择的任何设计库的经验。如果您对上述语言或框架不满意,请在继续之前花点时间了解基础知识。
React 中主题处理程序的组件
要实现构建可切换多色主题的目标,您的 Web 应用程序必须具备以下组件:
- 用于颜色状态管理的
Context Api
- 导航栏 (主题切换按钮)
- 页面元素的设计依赖项
- 不同主题的全局
less
文件
架构图
开始使用 React 进行主题开发
第 1 步:创建 React 应用程序
每次开发的第一步react
都是创建react
应用程序。这一步很容易实现。要创建您的react
应用程序,请在命令终端中运行如下所示的命令:
npx create-react-app demo-app
# or
yarn create-react-app demo-app
上面的 command
代码片段将创建您的 react
应用程序,安装默认的开发依赖项,并最终提供一个样板来开始您的应用程序开发。
第 2 步:编译 Antd 两种主题下的 less 文件
antd
主题配置大多数情况下我们会在 webpack
配置文件中配置对应 less loader
修改其中的 less
变量来实现基础的主题色等的自定义需求,其中 antd
还给我们提供了一种配置的 context api
,可以对组件配置国际化,自定义主题等功能。其中自定义主题(样式)可以通过修改内置的样式来实现,默认情况下,antd
组件内的样式名都是 ant
开头,可以通过官方提供的 ConfigProvider
来修改这一配置, 即修改 prefixCls
属性。这样我们可以通过运行时动态修改样式名前缀(白天模式跟黑夜模式各设置一个前缀)来实现白天跟黑夜模式的切换。antd
已经把 dark
模式的样式给我们了,现在,我们先来把两套样式文件提取出来放在我们项目中自己维护,这也是为了我们更加方便地去修改我们自定义所需要的颜色而不用通过修改全局样式去覆盖原有样式,这样即不用担心样式层级问题,也降低了维护成本。
注意:如果未安装less,则需要安装less:npm install -g less
进入项目根目录,执行以下命令:
# 白天模式
lessc --js --modify-var="ant-prefix=custom-light" node_modules/antd/dist/antd.variable.less custom-light.css
# 黑夜模式
lessc --js --modify-var="ant-prefix=custom-dark" node_modules/antd/dist/antd.dark.less custom-dark.css
执行完会在项目根目录(脚本执行时的目录)生成两份 css
文件,如图:
注意📢: 不建议把生成的antd的两份样式文件改为less文件,内容较多,走less-loader会导致修改全局样式后热更新非常慢!
其中,global.less
用于保留全局样式变量
第 3 步:创建主题上下文
在 theme/context
目录下新建一个用于处理全局 theme mode
变量、模式初始化、以及模式切换逻辑的 Provider
,其中重要的逻辑有以下三点:
初始化当前用户保存的模式,从本地缓存中查找,没有的话走最初始用户没设置过的逻辑——跟随当前用户系统的模式,重要知识点:
// 匹配浏览器的主题 window.matchMedia('(prefers-color-scheme: light)') // 获取初始主题模式 const getThemeMode = () => { return window.matchMedia('(prefers-color-scheme: light)').matches ? 'light' : 'dark' } // 监听浏览器(系统)主题变化 const matchMediaHandler = window.matchMedia('(prefers-color-scheme: light)') matchMediaHandler.addEventListener('change', changeColorMode)
每次用户手动切换主题后就跟随用户习惯走
const localKey = 'theme-mode' const getStorage = () => { const value = localStorage.getItem(localKey) return value ? value : getThemeMode() }
暴露给开发人员当前的
mode
以及切换mode
的switch
方法const switchModeHandler = (value: string) => { const doc = document.querySelector('body') if (doc) { doc.removeAttribute('class') doc.classList.add(value) setStorage(value) setMode(value) } } const contextValue: ThemeContextType = { mode: mode, switchMode: switchModeHandler }
包裹
antd
提供的ConfigProvider
,根据mode
的变幻动态传值修改prefixCls
的值以动态修改组件内的样式return ( // 自定义全局维护的主题 <ThemeContext.Provider value={contextValue}> {/*antd 的主题*/} <ConfigProvider prefixCls={`custom-${mode}`}> {props.children} </ConfigProvider> </ThemeContext.Provider> )
完整代码如下:
import React, { createContext, useEffect, useState } from 'react'
import { ConfigProvider } from 'antd'
const localKey = 'theme-mode'
const getStorage = () => {
const value = localStorage.getItem(localKey)
return value ? value : getThemeMode()
}
const setStorage = (value: string) => {
localStorage.removeItem(localKey)
localStorage.setItem(localKey, value)
}
const getThemeMode = () => {
return window.matchMedia('(prefers-color-scheme: light)').matches
? 'light'
: 'dark'
}
// CONTEXT
interface ThemeContextType {
mode: string
switchMode: (value: string) => void
}
export const ThemeContext = createContext<ThemeContextType>({
mode: '',
switchMode: () => {}
})
// PROVIDER
export const ThemeContextProvider: React.FC<{
children: JSX.Element
}> = props => {
const [mode, setMode] = useState('')
useEffect(() => {
const changeColorMode = () => {
const color = getThemeMode()
switchModeHandler(color)
}
const matchMediaHandler = window.matchMedia('(prefers-color-scheme: light)')
matchMediaHandler.addEventListener('change', changeColorMode)
switchModeHandler(getStorage())
return () =>
matchMediaHandler.removeEventListener('change', changeColorMode)
}, [])
const switchModeHandler = (value: string) => {
const doc = document.querySelector('body')
if (doc) {
doc.removeAttribute('class')
doc.classList.add(value)
setStorage(value)
setMode(value)
}
}
const contextValue: ThemeContextType = {
mode: mode,
switchMode: switchModeHandler
}
return (
// 自定义全局维护的主题
<ThemeContext.Provider value={contextValue}>
{/*antd 的主题*/}
<ConfigProvider prefixCls={`custom-${mode}`}>
{props.children}
</ConfigProvider>
</ThemeContext.Provider>
)
}
### 第 4 步:创建UI界面
第 5 步:提供界面组件需要的全局样式变量
在 theme/styles/global.less
新增如下变量:
:root {
--light-color: #fff;
--dark-color: #000;
}
body {
&.light {
--default-bg: var(--light-color);
--default-text-color: var(--dark-color);
}
&.dark {
--default-bg: var(--dark-color);
--default-text-color: var(--light-color);
}
}
应用全局样式变量,如 NavBar
组件的样式文件中(其他组件同理):
.container {
background: var(--default-bg);
color: var(--default-text-color);
}
第 6 步:切换主题
在 NavBar
组件中:
import React, { type FC, useContext } from 'react'
import cls from 'classnames'
import { SearchBar } from '@common/components'
import { ThemeContext } from '@src/theme/context'
import styles from './index.module.less'
const NavBar: FC = () => {
const { mode, switchMode } = useContext(ThemeContext)
return (
<div
className={cls(styles.container, styles[mode])}
>
<div className={styles.relativeWrap}>
<div
className={styles.themeSwitch}
onClick={() => switchMode(mode === 'dark' ? 'light' : 'dark')}
>
<img
src={
mode === 'dark'
? 'https://assets.blocksec.com/image/1664446020484-3.svg'
: 'https://assets.blocksec.com/image/1664446020484-2.svg'
}
alt=""
/>
</div>
</div>
</div>
)
}
export default NavBar
结论
在本文中,我们应用了各种概念来使用 React
创建多主题。我们还使用react-context-api
.
本文涵盖的概念可用于未来涉及颜色和主题管理的项目。
我希望您发现这些内容对您的编程工作有帮助。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。