scss实现多主题样式切换

实现

  • 在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;
}
相关推荐
拾光拾趣录4 分钟前
算法 | 下一个更大的排列
前端·算法
小屁孩大帅-杨一凡30 分钟前
如何使用Python将HTML格式的文本转换为Markdown格式?
开发语言·前端·python·html
于慨31 分钟前
uniapp云打包安卓
前端·uni-app
米粒宝的爸爸31 分钟前
【uniapp】使用uviewplus来实现图片上传和图片预览功能
java·前端·uni-app
LaoZhangAI32 分钟前
2025年虚拟信用卡订阅ChatGPT Plus完整教程(含WildCard停运后最新方案)
前端·后端
雪碧聊技术33 分钟前
Uniapp 纯前端台球计分器开发指南:能否上架微信小程序 & 打包成APP?
前端·微信小程序·uni-app·台球计分器
清风细雨_林木木34 分钟前
Vuex 的语法“...mapActions([‘login‘]) ”是用于在组件中映射 Vuex 的 actions 方法
前端·javascript·vue.js
会功夫的李白37 分钟前
Uniapp之自定义图片预览
前端·javascript·uni-app·图片预览
拾光拾趣录1 小时前
script 标签上有那些属性,分别作用是啥?
前端·javascript
码农胖大海1 小时前
前端搞基建之低代码平台再调研
前端·低代码