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 小时前
从零开发一个 Coding Agent(四):使用状态机校验大模型事件流
前端·人工智能·后端
Csvn5 小时前
🧩 ESM vs CJS 混用的 7 个「天坑」——从 TypeScript 编译到 Node 与浏览器
前端
Csvn5 小时前
🎯 Web 性能 API 集合:Performance Observer 的 5 个冷门妙用
前端
Csvn5 小时前
深入 React 闭包陷阱:从根源上理解并根治 stale closure
前端
whyfail5 小时前
前端学 Spring Boot(8):接口为什么越用越慢?
前端·spring boot·后端
用户059540174466 小时前
LangChain 记忆测试踩坑实录:这两个坑让我排查了 4 小时
前端·css
程序员黑豆6 小时前
鸿蒙应用开发:@Monitor 装饰器使用教程
前端·harmonyos
SamChan907 小时前
在Web应用中集成PDF多语言翻译功能:PDFTranslator API实战指南
前端·python·ai·pdf·yapi·机器翻译
kyriewen7 小时前
AI Agent 9秒删光了生产数据库——我给自己的项目做了5个紧急检查
前端·ai编程·claude
IT_陈寒7 小时前
JavaScript的this又双叒叕让我怀疑人生了
前端·人工智能·后端