我有一个VueJS应用程序,它将包含许多不同的主题(至少20个左右).每个主题样式表不仅会改变 colored颜色 和字体大小,还会改变某些元素的位置和布局.

我希望用户能够在这些主题之间动态切换.因此,在运行时,用户将能够打开选项菜单并从下拉列表中进行 Select .

What is the cleanest way to have many dynamic user-selectable themes in VueJS?


我想到了几种方法,比如:

  • 动态插入<link><style>标签.虽然这可能有效,但我并不认为它特别"干净",如果我从AJAX加载,那么通常我会看到FOUC.
  • 只需通过计算(computed)属性更改Vue类绑定.比如每个组件中每个支持的主题都有一个if-else链.我并不特别喜欢这个解决方案,因为这样一来,我制作的每个组件都需要在以后每次添加新主题时进行更新.

在React中,我认为有一个插件或其他东西有一个<ThemeProvider>个组件,其中添加一个主题就像包装它一样简单,即<ThemeProvider theme={themeProp}><MyComponent></ThemeProvider>,该主题中的所有样式都将应用于该组件和所有子组件.

VueJS是否有类似的功能,或者有没有实现的方法?

推荐答案

我承认我在这件事上玩得很开心.此解决方案在Vue上不支持depend,但在Vue上可以轻松使用by.开始!

我的目标是创建一个"特别干净"的动态插入<link>个样式表,这不应该导致FOUC个.

我创建了一个名为ThemeHelper的类(从技术上讲,它是一个构造函数,但你知道我的意思),其工作原理如下:

  • myThemeHelper.add(themeName, href)将从href(一个URL)和stylesheet.disabled = true预加载一个样式表,并给它一个名称(只是为了跟踪它).当调用样式表的onload时,返回一个解析为CSSStyleSheetPromise.
  • myThemeHelper.theme = "<theme name>"(setter) Select 要应用的主题.上一个主题被禁用,而给定的主题被启用.切换发生得很快,因为.add已经预加载了样式表.
  • myThemeHelper.theme(getter)返回当前主题名.

这门课本身有33行.我制作了一个片段,在一些 bootstrap 样本主题之间切换,因为这些CSS文件非常大(100Kb+).

const ThemeHelper = function() {
 
  const preloadTheme = (href) => {
    let link = document.createElement('link');
    link.rel = "stylesheet";
    link.href = href;
    document.head.appendChild(link);
    
    return new Promise((resolve, reject) => {
      link.onload = e => {
        const sheet = e.target.sheet;
        sheet.disabled = true;
        resolve(sheet);
      };
      link.onerror = reject;
    });
  };
  
  const selectTheme = (themes, name) => {
    if (name && !themes[name]) {
      throw new Error(`"${name}" has not been defined as a theme.`); 
    }
    Object.keys(themes).forEach(n => themes[n].disabled = (n !== name));
  }
  
  let themes = {};

  return {
    add(name, href) { return preloadTheme(href).then(s => themes[name] = s) },
    set theme(name) { selectTheme(themes, name) },
    get theme() { return Object.keys(themes).find(n => !themes[n].disabled) }
  };
};

const themes = {
  flatly: "https://bootswatch.com/4/flatly/bootstrap.min.css",
  materia: "https://bootswatch.com/4/materia/bootstrap.min.css",
  solar: "https://bootswatch.com/4/solar/bootstrap.min.css"
};

const themeHelper = new ThemeHelper();

let added = Object.keys(themes).map(n => themeHelper.add(n, themes[n]));

Promise.all(added).then(sheets => {
  console.log(`${sheets.length} themes loaded`);
  themeHelper.theme = "materia";
});
<h3>Click a button to select a theme</h3>

<button 
  class="btn btn-primary" 
  onclick="themeHelper.theme='materia'">Paper theme
  </button>
  
<button 
  class="btn btn-primary" 
  onclick="themeHelper.theme='flatly'">Flatly theme
</button>

<button 
  class="btn btn-primary" 
  onclick="themeHelper.theme='solar'">Solar theme
</button>

不难看出我完全是ES6(也许我有点过度使用了const:)

就Vue而言,您可以制作一个组件来包装<select>:

const ThemeHelper = function() {
 
  const preloadTheme = (href) => {
    let link = document.createElement('link');
    link.rel = "stylesheet";
    link.href = href;
    document.head.appendChild(link);
    
    return new Promise((resolve, reject) => {
      link.onload = e => {
        const sheet = e.target.sheet;
        sheet.disabled = true;
        resolve(sheet);
      };
      link.onerror = reject;
    });
  };
  
  const selectTheme = (themes, name) => {
    if (name && !themes[name]) {
      throw new Error(`"${name}" has not been defined as a theme.`); 
    }
    Object.keys(themes).forEach(n => themes[n].disabled = (n !== name));
  }
  
  let themes = {};

  return {
    add(name, href) { return preloadTheme(href).then(s => themes[name] = s) },
    set theme(name) { selectTheme(themes, name) },
    get theme() { return Object.keys(themes).find(n => !themes[n].disabled) }
  };
};

let app = new Vue({
  el: '#app',
  data() {
    return {
      themes: {
        flatly: "https://bootswatch.com/4/flatly/bootstrap.min.css",
        materia: "https://bootswatch.com/4/materia/bootstrap.min.css",
        solar: "https://bootswatch.com/4/solar/bootstrap.min.css"
      },
      themeHelper: new ThemeHelper(),
      loading: true,
    }
  },
  created() {
    // add/load themes
    let added = Object.keys(this.themes).map(name => {
      return this.themeHelper.add(name, this.themes[name]);
    });

    Promise.all(added).then(sheets => {
      console.log(`${sheets.length} themes loaded`);
      this.loading = false;
      this.themeHelper.theme = "flatly";
    });
  }
});
<script src="https://unpkg.com/vue@2.5.2/dist/vue.js"></script>

<div id="app">
  <p v-if="loading">loading...</p>

  <select v-model="themeHelper.theme">
    <option v-for="(href, name) of themes" v-bind:value="name">
      {{ name }}
    </option>
  </select>
  <span>Selected: {{ themeHelper.theme }}</span>
</div>

<hr>

<h3>Select a theme above</h3>
<button class="btn btn-primary">A Button</button>

我希望这对你有用,对我来说也很有趣!

Vue.js相关问答推荐

在vuejs中使用表单追加时如何使用布尔值

在 VueJS 中,将变量而不是组件的字符串名称传递给动态组件的 :is 属性不起作用

有没有办法初始化一个保留初始 HTML 的 Vue 对象

在 Vue 中启用(控制台)记录路由事件

Eslint Vue 3 解析错误:'>' expected.eslint

使用 Vue 和 JS 下载 CSV 文件

Vue:限制文本区域输入中的字符,截断过滤器?

v-on:submit.prevent 不停止表单提交

Vue&TypeScript:在项目目录外的 TypeScript 组件中实现导入时如何避免错误 TS2345?

NuxtServerInit 在 Vuex 模块模式下不起作用 - Nuxt.js

使用vue.js单击时如何获取按钮的值

如何禁用 nuxt 默认错误重定向

Proxy代理在 Vue 3 的控制台中是什么意思?

如何将项目推送到 Vuejs 中数据对象的数组中? Vue 似乎没有关注 .push() 方法

无法访问数据对象的嵌套属性

如何避免在 Vue 中一直写 this.$store.state.donkey 的需要?

Vue.js 单向绑定表单

表格行上的 Vuejs 转换

在~/components/AddPlaceModal.vue中找不到导出AddPlaceModal

如何修复套接字 io 中的 400 错误错误请求?