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;
}
相关推荐
mengchanmian21 小时前
前端node常用配置
前端
华洛1 天前
利好打工人,openclaw不是企业提效工具,而是个人助理
前端·javascript·产品经理
xkxnq1 天前
第六阶段:Vue生态高级整合与优化(第93天)Element Plus进阶:自定义主题(变量覆盖)+ 全局配置与组件按需加载优化
前端·javascript·vue.js
A黄俊辉A1 天前
vue css中 :global的使用
前端·javascript·vue.js
小码哥_常1 天前
被EdgeToEdge适配折磨疯了,谁懂!
前端
小码哥_常1 天前
从Groovy到KTS:Android Gradle脚本的华丽转身
前端
灵感__idea1 天前
Hello 算法:复杂问题的应对策略
前端·javascript·算法
麦麦鸡腿堡1 天前
JavaWeb_请求参数,设置响应数据,分层解耦
java·开发语言·前端
Dxy12393102161 天前
CSS常用样式详解:从基础到进阶的全面指南
前端·css
IT_陈寒1 天前
SpringBoot自动配置揭秘:5个让开发效率翻倍的隐藏技巧
前端·人工智能·后端