主题切换
前端开发中,主题切换逐渐成为一种常见需求。用户可能希望根据自己的喜好、习惯或者场景需求进行主题切换,例如切换日间与夜间模式。本文主要介绍如何在前端实现主题切换,以及一些应用实例。
link 动态引入 (不推荐)
提前准备好各个主题的 CSS 文件,在切换的时候加载,比如:
优缺点
- 优点
- 实现简单,只需要关注维护 css 即可
- 可以实现主题样式按需加载
- 缺点
- 如果 css 文件过大, 可能会导致页面样式闪烁
- 各个主题文件相互独立,修改样式比较麻烦
- 服务端渲染时,没办法直接操作客户端 DOM 元素,会影响加载性能
- 不够灵活,不能满足现代前端开发的需求
提前加载样式,类名切换(不推荐)
先引入各个主题的 css,通过切换类名实现主题切换。 原理是切换主题时,引入不同的主题算法(css 文件)
jsreturn ( <ConfigProvider theme={{ algorithm: themeLight ? theme.defaultAlgorithm : theme.darkAlgorithm, }} > {children} </ConfigProvider> );
相同元素,类名不同使用不同的css主题文件
还可以配置 前缀
jsexport const PREFIX = 'tech-theme'; ... <ConfigProvider prefixCls={prefixCls} > {children} </ConfigProvider>
优缺点
- 优点 - 样式切换会更加顺畅 - 便于组件封装与状态管理 - 可配置性比较强,支持动态自定义样式
- 缺点 - 首屏加载会慢一些 - 除了样式切换不会闪烁,其余缺点与动态 link 方案缺点一样
css 变量(推荐)
这个方案是目前主流推荐的方案。在介绍之前,先讲一下浏览器的几个新特性。
下面有实现方式
优缺点
- 优点 - 原生支持,是现在主流方案 - 轻量级,部署方便,可定制化程度高 - 不存在优先级冲突问题 - 可实现热替换和更改
- 缺点 - 老的浏览器存在兼容性问题
css 预处理器(可以考虑)
将所有的样式文件使用 Sass 预先定义好,然后写一个 js 脚本在打包前编译为 css,然后将这个文件动态插入到 head 里
优缺点
- 优点 - 样式切换流畅,不会卡顿 - 语法更多样,开发成本低 - 与 css 变量一样,新增或修改样式,只需要改动 Scss/Less 变量即可
- 缺点 - 需要在编译时手动编译,运行时没办法热替换 - 学习成本高一些
css-in-js (可以考虑)
与 Antd 的实现理念一致,也是维护一个 ThemeProvider 来完成可配置的主题设置
优缺点
优点 - 不会存在 css 加载部署的问题,适合微前端这种需要隔离样式的场景 - ...[Antd 方案优点]
缺点 - 学习曲线比较高 - 增加了运行时的开销和打包体积 - 源码可读性可能会变差
css 框架(Tailwindcss)
使用 css 框架,原则上不算是一个新技术分类,他还是上面所讲的方案中的一种或几种,是对这些方案的企业级封装
优缺点
- 优点 - 大势所趋,前景广阔 - 易于管理和维护,使得定制主题变得随心所欲 - 代码冗余小 - 更好的性能与可扩展性
- 缺点 - 框架本身可能会与已有项目架构冲突 - 框架约束了可定制的灵活性 - 学习成本较高 - 需要有额外的维护版本升级工作
主题切换设计原则
1. 应遵循用户使用流畅原则,不应添加额外的使用负担
比如 页面中由用户定制化的内容,主题切换后导致色差而无法看清;或者切换按钮藏得太深,用户找不到等
2. 不应过度影响页面加载速度
比如,样式 css 太大,网页的白屏速度大大增加,反而影响了使用体验
3. 应根据业务场景和用户群体决定
比如,客户群体是 To B 的商务人士,年轻人很少,这种花里胡哨的切换反而不适应,对于商旅人士,移动端适配做得好可能更重要。
方案1. css变量切换主题
CSS变量是一种实现主题切换的便捷方法。通过在CSS中定义变量,可以方便地修改主题颜色,例如:
:root {
--primary-color: #42b983;
--secondary-color: #35495e;
}
.button {
background-color: var(--primary-color);
color: var(--secondary-color);
}
在JavaScript中,可以通过修改CSS变量的值实现主题切换:
function switchTheme(theme) {
if (theme === 'dark') {
document.documentElement.style.setProperty('--primary-color', '#1c2022');
document.documentElement.style.setProperty('--secondary-color', '#ffffff');
} else {
document.documentElement.style.setProperty('--primary-color', '#42b983');
document.documentElement.style.setProperty('--secondary-color', '#35495e');
}
}
方案2. CSS类名切换
另一种实现主题切换的方法是使用不同的CSS类名。首先,需要在CSS中定义各个主题的样式:
.light-theme {
--primary-color: #42b983;
--secondary-color: #35495e;
}
.dark-theme {
--primary-color: #1c2022;
--secondary-color: #ffffff;
}
.button {
background-color: var(--primary-color);
color: var(--secondary-color);
}
接下来,在JavaScript中通过切换元素的类名来实现主题切换:
function switchTheme(theme) {
const root = document.documentElement;
if (theme === 'dark') {
root.classList.add('dark-theme');
root.classList.remove('light-theme');
} else {
root.classList.add('light-theme');
root.classList.remove('dark-theme');
}
}
方案3. CSS预处理器
CSS预处理器(如Sass、Less等)提供了更多灵活性,可以通过变量、混合(mixin)等特性实现主题切换。例如,使用Sass定义主题样式:
$light-primary-color: #42b983;
$light-secondary-color: #35495e;
$dark-primary-color: #1c2022;
$dark-secondary-color: #ffffff;
@mixin theme($primary-color, $secondary-color) {
.button {
background-color: $primary-color;
color: $secondary-color;
}
}
.light-theme {
@include theme($light-primary-color, $light-secondary-color);
}
.dark-theme {
@include theme($dark-primary-color, $dark-secondary-color);
}
与CSS类名切换方法相似,在JavaScript中切换元素的类名即可实现主题切换。
4. 本地存储 (样式加载优化)
在实际应用中,用户可能希望浏览器记住他们的主题选择。这时,我们可以将用户的主题选择保存在localStorage中。在页面加载时,检查localStorage中是否有用户的主题选择,如果有,则应用相应的主题。以下是一个简单的示例
function saveThemeToLocalStorage(theme) {
localStorage.setItem('theme', theme);
}
function getThemeFromLocalStorage() {
return localStorage.getItem('theme');
}
function applyTheme(theme) {
const root = document.documentElement;
if (theme === 'dark') {
root.classList.add('dark-theme');
root.classList.remove('light-theme');
} else {
root.classList.add('light-theme');
root.classList.remove('dark-theme');
}
}
// 页面加载时,应用主题
document.addEventListener('DOMContentLoaded', () => {
const savedTheme = getThemeFromLocalStorage();
if (savedTheme) {
applyTheme(savedTheme);
} else {
// 如果没有保存的主题,可以应用默认主题
applyTheme('light');
}
});
// 主题切换按钮
document.getElementById('switch-theme').addEventListener('click', () => {
const currentTheme = getThemeFromLocalStorage();
if (currentTheme === 'dark') {
applyTheme('light');
saveThemeToLocalStorage('light');
} else {
applyTheme('dark');
saveThemeToLocalStorage('dark');
}
});
5. 自定义主题
除了提供预定义的主题外,还可以允许用户自定义主题。例如,通过输入框获取用户自定义颜色,并实时应用主题。以下是一个简单的示例:
<label for="primary-color">Primary color:</label>
<input type="color" id="primary-color" value="#42b983">
<label for="secondary-color">Secondary color:</label>
<input type="color" id="secondary-color" value="#35495e">
<button id="apply-custom-theme">Apply custom theme</button>
function applyCustomTheme(primaryColor, secondaryColor) {
document.documentElement.style.setProperty('--primary-color', primaryColor);
document.documentElement.style.setProperty('--secondary-color', secondaryColor);
}
document.getElementById('apply-custom-theme').addEventListener('click', () => {
const primaryColor = document.getElementById('primary-color').value;
const secondaryColor = document.getElementById('secondary-color').value;
applyCustomTheme(primaryColor, secondaryColor);
});
如何选择
在实践中,可以根据项目的具体需求选择合适的主题切换方案。例如,如果项目中已经使用了CSS预处理器,那么可以考虑使用相应的预处理器特性来实现主题切换。如果项目对浏览器兼容性要求较高,可以选择CSS类名切换方案。而如果希望实现更简洁的代码和更好的性能,CSS变量是一个不错的选择
React中主题切换
在React中实现主题切换通常涉及到定义一组主题变量,并在用户选择不同主题时切换这些变量。这可以通过使用React的上下文(Context)API和钩子(Hooks)API来实现。以下是一个基本的示例,展示了如何在React应用程序中实现主题切换。
首先,你需要定义你的主题变量。这通常是一组CSS变量或者JavaScript对象,包含了颜色、字体大小等样式属性。
// themes.js 主题颜色
export const lightTheme = {
body: '#FFF',
text: '#363537',
toggleBorder: '#FFF',
background: '#363537',
};
export const darkTheme = {
body: '#363537',
text: '#FAFAFA',
toggleBorder: '#6B8096',
background: '#999',
};
接下来,创建一个上下文来保存当前主题和一个切换主题的函数。
// ThemeContext.js
import React, { createContext, useState } from 'react';
import { lightTheme, darkTheme } from './themes';
export const ThemeContext = createContext({
theme: lightTheme,
toggleTheme: () => {},
});
export const ThemeProvider = ({ children }) => {
const [theme, setTheme] = useState(lightTheme);
const toggleTheme = () => {
if (theme === lightTheme) {
setTheme(darkTheme);
} else {
setTheme(lightTheme);
}
};
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
};
现在,你可以在你的应用程序中使用ThemeProvider来包裹你的组件,并使用ThemeContext来访问当前主题和切换主题的函数。
// App.js 使用
import React, { useContext } from 'react';
import { ThemeProvider, ThemeContext } from './ThemeContext';
const ThemeToggle = () => {
const { toggleTheme } = useContext(ThemeContext);
return (
<button onClick={toggleTheme}>Toggle Theme</button>
);
};
const App = () => {
return (
<ThemeProvider>
<ThemeToggle />
{/* 其他组件 */}
</ThemeProvider>
);
};
export default App;
最后,你需要确保你的组件使用上下文中的主题变量来设置样式。这可以通过内联样式、CSS-in-JS库,或者传统的CSS样式表与CSS变量结合使用来实现。
// 使用内联样式
const ThemedComponent = () => {
const { theme } = useContext(ThemeContext);
const style={{ backgroundColor: theme.body, color: theme.text }}
return ( <div style >
{/* 组件内容 */}
</div>
);
};
或者,如果你使用的是styled-components,你可以这样做:
// 使用styled-components
import styled from 'styled-components';
import { useContext } from 'react';
import { ThemeContext } from './ThemeContext';
const StyledComponent = styled.div`
background-color: ${({ theme }) => theme.body};
color: ${({ theme }) => theme.text};
`;
const ThemedComponent = () => {
const { theme } = useContext(ThemeContext);
return (
<StyledComponent theme={theme}>
{/* 组件内容 */}
</StyledComponent>
);
};
这个基本的示例展示了如何在React中实现主题切换。你可以根据需要扩展这个示例,添加更多的主题属性,或者实现更复杂的主题切换逻辑。