实现
- 在html中定义一个data-theme属性,设置一个默认主题,每次切换主题时都将属性存储在localStorage中
- 确定主题映射表, 包括多个主题,light\dark等
scss
// 主题映射表
$themes: (
light: ( primary-color: #409EFF, ... ),
dark: ( primary-color: #165DFF, ... )
)
- 封装一个主题混入的mixin方法,整个mixin会遍历所有主题,在对应的主题类名下应用样式,比如当前是light主题,就会应用light主题样式
scss
@mixin apply-theme() {
@each $theme-name, $theme-map in $themes {
// 使用属性选择器匹配
data-theme [data-theme="#{$theme-name}"] & {
// 临时将主题变量设为全局
$theme-map: $theme-map !global;
// 插入内容块
@content; }
}
}
// 获取主题变量的辅助函数
@function theme-var($key) {
@return map-get($theme-map, $key);
}
- 在具体UI组件中使用对应的变量名
scss
.button {
padding: 8px 16px; border-radius: 4px;
cursor: pointer; transition: all 0.3s;
@include apply-theme() {
background-color: theme-var('primary-color');
color: white;
border: 1px solid theme-var('primary-color');
&:hover { opacity: 0.8; }
}
}
目录结构

> > 问题: 这种方式引入会将全部主题的样式文件一次性引入,属于牺牲首屏但是每次切换不需要重新拉去样式表;那如何做到按模块按需加载呢?
按需引入方案

1、主题入口文件示例
scss
// light/index.scss
@use '../base/core';
@use './vars' as light;
:root, [data-theme="light"] {
--primary-color: #{light.$primary-color};
--background-color: #{light.$background-color};
--text-color: #{light.$text-color};
}
// 主题特有样式
[data-theme="light"] {
.special-element {
border-color: light.$special-border;
}
}
2、webpack中的配置
js
const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
entry: {
main: './src/main.js',
'theme-light': './src/styles/themes/light.scss',
'theme-dark': './src/styles/themes/dark.scss'
},
output: {
filename: '[name].js',
path: path.resolve(__dirname, 'dist'),
publicPath: '/'
},
optimization: {
splitChunks: {
cacheGroups: {
styles: {
name: 'styles',
test: /\.css$/,
chunks: 'all',
enforce: true
}
}
}
},
module: {
rules: [
{
test: /\.scss$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'sass-loader'
]
}
]
},
plugins: [
new MiniCssExtractPlugin({
filename: '[name].css',
chunkFilename: '[id].css'
})
]
};
3、智能主题加载器实现
theme-loader.js:
javascript
class ThemeLoader {
constructor() {
this.loadedThemes = new Set(['light']); // 默认加载light主题
this.initTheme();
}
async initTheme() {
const savedTheme = localStorage.getItem('theme') || 'light';
if (savedTheme !== 'light') {
await this.loadTheme(savedTheme);
}
document.documentElement.dataset.theme = savedTheme;
}
async loadTheme(themeName) {
if (this.loadedThemes.has(themeName)) return;
try {
const linkId = `theme-${themeName}`;
if (document.getElementById(linkId)) return;
return new Promise((resolve, reject) => {
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = `/theme-${themeName}.css`;
link.id = linkId;
link.onload = () => {
this.loadedThemes.add(themeName);
resolve();
};
link.onerror = reject;
document.head.appendChild(link);
});
} catch (err) {
console.error(`Failed to load ${themeName} theme:`, err);
throw err;
}
}
async switchTheme(themeName) {
if (!this.loadedThemes.has(themeName)) {
await this.loadTheme(themeName);
}
document.documentElement.dataset.theme = themeName;
localStorage.setItem('theme', themeName);
}
}
export const themeLoader = new ThemeLoader();
4、nginx配置缓存
nginx
location /themes {
# 主题CSS长期缓存
add_header Cache-Control "public, max-age=31536000, immutable";
try_files $uri =404;
}
location / {
# 主文件常规缓存
add_header Cache-Control "public, max-age=3600";
try_files $uri /index.html;
}