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;
}
相关推荐
JayceM1 小时前
Vue中v-show与v-if的区别
前端·javascript·vue.js
HWL56791 小时前
“preinstall“: “npx only-allow pnpm“
运维·服务器·前端·javascript·vue.js
德育处主任2 小时前
p5.js 掌握圆锥体 cone
前端·数据可视化·canvas
mazhenxiao2 小时前
qiankunjs 微前端框架笔记
前端
无羡仙2 小时前
事件流与事件委托:用冒泡机制优化前端性能
前端·javascript
秃头小傻蛋2 小时前
Vue 项目中条件加载组件导致 CSS 样式丢失问题解决方案
前端·vue.js
CodeTransfer2 小时前
今天给大家搬运的是利用发布-订阅模式对代码进行解耦
前端·javascript
睡不着先生2 小时前
`text-wrap: balance` 实战指南:让多行标题自动排版更优美
css
阿邱吖2 小时前
form.item接管受控组件
前端
韩劳模2 小时前
基于vue-pdf实现PDF多页预览
前端