方案一:更改body的class命名
通过修改body标签的class名称来进行主题的切换,这是最简单最容易理解的方式。
样式代码如下:
<style>
.light-theme p {
color: #FFFFFF;
}
.light-theme div {
background-color: #000000;
}
.dark-theme div {
color: #f4f5f5;
}
.dark-theme p {
color: #333333;
}
</style>
body代码如下:
...
<body class="normal-theme">
<div>
<p>这是测试</p>
</div>
<div id="root"></div>
</body>
...
React代码如下:
const [ theme, setTheme ] = useState(false)
const changeTheme = () => {
console.log('切换主题')
setTheme(!theme)
document.getElementsByTagName('body')[0].className = theme ? 'dark-theme' : 'light-theme'
}
优点: 容易理解和实现
缺点: 多主题的时候,需要些多个class,代码容易混乱;需要手动编写
方案二:利用less的modifyVars
- 通过less的
modifyVars方法改变对应的样式变量,因此我们先需要引入less.js,可以通过npm install less的方式或者CND的方式引入less.js
<script src="https://cdnjs.cloudflare.com/ajax/libs/less.js/3.11.1/less.min.js"></script>
这样就可以在项目中使用modifyVars方法了。
- 创建public文件夹,在里面继续创建一个
header.less文件和style.less文件,其中header.less用来写代码中使用到的样式,style.less作为所有的样式入口。
header.less的内容为:
@primary-color: #15eaf0; // 全局主色
@success-color: #52c41a; // 成功色
.header {
height: 80px;
background-color: @primary-color;
color: @success-color;
}
style.less的内容为:
// 这里引入header.less是为了把style.less当作整体入口来使用
// 后续像footer.less这种样式文件都在这个文件里面引入
// @import './footer.less';
@import './header.less';
- 在切换入口html文件中,引入
style.less:
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no, minimal-ui" />
<title>xxxx</title>
</head>
<body>
<!-- 这里的link要写在body里面,要是通过CDN引入less.js的话,一定要将其放到这个link后面 -->
<link rel="stylesheet/less" type="text/css" href="../../public/style.less" />
<!-- <script src="https://cdnjs.cloudflare.com/ajax/libs/less.js/3.11.1/less.min.js"></script> -->
<div id="root"></div>
</body>
</html>
- 最后在切换主题的事件中调用
modifyVars方法
import '../../public/header.less'
import less from 'less'
...
const changeTheme = () => {
less.modifyVars({ // 调用 `less.modifyVars` 方法来改变变量值
'primary-color': '#Ff4FFF',
}).then(() => {
console.log('修改成功');
})
}
...
注意点:
- 样式文件在html中引入的位置最好放在
body标签之内,如果通过CDN的方式引入less.js,那么样式文件一定要在script标签之前引入。 - 在webpack中配置
publicPath: '/public/',配置后webpack 会在静态文件路径前面添加publicPath的值,这里可以防止打包后本地资源文件引入的问题。本项目用的是umijs,因此在umirc.ts中进行配置:
export default defineConfig({
...
publicPath: '/public/'
...
})
优点: 样式颜色修改灵活
缺点: 无法压缩混淆
方案三:使用styled-components
styled-component提供<ThemeProvider>包装组件以支持主题,<ThemeProvider>通过contextAPI 为其后代组件提供主题.在其渲染树中的所有组件都能够访问主题。具体实现如下:
- 首先通过
npm install --save styled-components安装styled-componets,安装成功之后创建styled.ts,在文件中写一个基本样式组件:
import styled from 'styled-components'
export const HeaderDiv = styled.div`
background: ${props => props.theme.backgroundColor};
`
- 使用
<ThemeProvider>,将其放在HeaderDiv外层:
import React, { useState } from 'react'
import { SkinOutlined } from '@ant-design/icons'
import { ThemeProvider } from 'styled-components'
import { HeaderDiv } from './styled'
const Header: React.FC = props => {
const [ theme, setTheme ] = useState({
backgroundColor: 'green'
})
const changeTheme = () => {
console.log('切换主题')
setTheme({
backgroundColor: 'red'
})
}
return (
<ThemeProvider theme={theme}>
<HeaderDiv>
<SkinOutlined onClick={changeTheme} style={{ marginRight: 10 }} />
</HeaderDiv>
<ThemeProvider>
)
}
export default Header
优点: 可以使用ts,用来封装标准组件方便,不存在兼容性问题
缺点: 对antd的组件,需要二次封装
方案四:使用css变量
通过定义css变量的方式来动态修改对应的颜色,或者修改对应的某个主题;
- 首先是改变单个颜色变量的做法:
- 在你的样式文件中定义css变量并且使用,我这里在
header.less中使用
:root {
--color: #333333;
--background-color: #FFFFFF;
}
.header {
color: var(--color);
background-color: var(--background-color);
}
- 修改变量的一些方法
const root = document.documentElement
let _style = getComputedStyle(root)
// 获取颜色变量
_style.getPropertyValue('--background-color').trim()
// 设置颜色变量
root.style.setProperty('--background-color','green')
// 删除颜色变量
root.style.removeProperty('--background-color')
- 最终在React中的使用
import React from 'react'
import { SkinOutlined } from '@ant-design/icons'
import style from './header.less'
const Header: React.FC = props => {
const changeTheme = () => {
console.log('切换主题')
const root = document.documentElement
let _style = getComputedStyle(root)
root.style.setProperty('--background-color','green')
}
return (
<div className={style.header}>
<SkinOutlined onClick={changeTheme} />
</div>
)
}
export default Header
- 接下来是改变整个主题的用法:
- 将你的主题文件创建到public文件夹中的
variable.css中(没错,是.css,这里用less的话无法生效)
// variable.css
:root.light{
--color: #333333;
--background-color: #FFFFFF;
}
:root.dark {
--color: #FFFFFF;
--background-color: #000000;
}
- 在
header.less中使用对应变量:
.header {
color: var(--color);
background-color: var(--background-color);
}
- 在React中写切换事件
import React, { useEffect } from 'react'
import { SkinOutlined } from '@ant-design/icons'
import style from './header.less'
const Header: React.FC = props => {
useEffect(() => {
// 这里设置默认的主题样式
const root = document.documentElement
root.className = 'light'
}, [])
const changeTheme = () => {
console.log('切换主题')
const root = document.documentElement
// 这里跟上面改变单个变量的方式不同,需要直接改变root的className来改变整体主题
root.className = 'dark'
}
return (
<div className={style.header}>
<SkinOutlined onClick={changeTheme} style={{ marginRight: 10 }} />
</div>
)
}
export default Header
- 注意还要将
variable.css文件引入到html文件中,并且配置webpack的publicPath为/public/,就如上述方案二中的配置方式一样。
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no, minimal-ui" />
<link rel="stylesheet" type="text/css" href="../../public/variable.css" />
</head>
<body>
<div id="root"></div>
</body>
</html>
缺点: 兼容性存在一定问题
关于兼容性的话,可以试试使用
postcss-custom-properties和css-vars-ponyfill两款插件,具体我还没尝试过,不做过多赘述。。
关于ant-design
关于antd这方面它本身支持的在线主题切换方案好像还存在一些小问题,后续成熟了再看看
更多方案:理论(未实践)
- 利用JS事件监听媒体查询动态改变网页主题样式
- 参考ElementUi的更换主题方案
- 改造 webpack插件 style-resource-loader