问题描述
限时送ChatGPT账号..我有一个 VueJS 应用程序,它将带有许多不同的主题(至少 20 个左右).每个主题样式表不仅会更改颜色和字体大小等内容,还会更改某些元素的位置和布局.
我希望用户能够在这些主题之间动态切换.因此,在运行时,用户将能够打开选项"菜单并从下拉列表中进行选择.
在 VueJS 中拥有许多动态用户可选主题的最简洁方法是什么?
<小时>我想到了几种方法,例如:
动态插入
或
标签.虽然这可能有效,但我并不认为它特别干净",如果我从 AJAX 加载,那么我经常会看到 FOUC.只需通过计算属性更改 Vue 类绑定.就像为每个组件中的每个支持的主题都有一个 if-else
链.我不是特别喜欢这个解决方案,因为以后每次添加新主题时,我制作的每个组件都需要更新.在 React 中,我认为有一个插件或具有 <ThemeProvider>
组件的东西,其中添加主题就像包装它一样简单,即 <ThemeProvider theme={themeProp}><MyComponent></ThemeProvider>
,并且该主题中的所有样式都将应用于该组件和所有子组件.
VueJS 有没有类似的东西,或者有没有实现的方法?
解决方案我承认我对这个方案很感兴趣.这个解决方案不依赖 Vue,但它可以很容易地被by Vue 使用.来了!
我的目标是创建一个特别干净"的 样式表的动态插入,它不应导致 FOUC.
我创建了一个名为 ThemeHelper
的类(从技术上讲,它是一个构造函数,但您知道我的意思),其工作方式如下:
myThemeHelper.add(themeName, href)
将使用 stylesheet.disabled = true
从 href
(一个 URL)预加载一个样式表,并且给它一个名字(只是为了跟踪它).这将返回一个 Promise
解析为 CSSStyleSheet 当样式表的 onload
被调用时.myThemeHelper.theme = "<主题名称>"
(setter) 选择要应用的主题.上一个主题被禁用,而给定的主题被启用.切换发生得很快,因为样式表已经由 .add
预加载.myThemeHelper.theme
(getter) 返回当前主题名称.类本身有 33 行.我制作了一个在一些 Bootswatch 主题之间切换的片段,因为这些 CSS 文件非常大(100Kb+).
const ThemeHelper = function() {const preloadTheme = (href) =>{let link = document.createElement('link');link.rel = "样式表";链接.href = href;document.head.appendChild(link);返回新的承诺((解决,拒绝)=> {link.onload = e =>{const sheet = e.target.sheet;sheet.disabled = true;解决(工作表);};link.onerror = 拒绝;});};const selectTheme = (themes, name) =>{如果(名称&& !主题[名称]){throw new Error(`"${name}" 尚未定义为主题.`);}Object.keys(themes).forEach(n => themes[n].disabled = (n !== name));}让主题 = {};返回 {add(name, href) { return preloadTheme(href).then(s => themes[name] = s) },设置主题(名称){选择主题(主题,名称)},获取主题() { 返回对象.keys(主题).find(n => !themes[n].disabled) }};};const 主题 = {断然:https://bootswatch/4/flatly/bootstrap.min.css",材料:https://bootswatch/4/materia/bootstrap.min.css",太阳能:https://bootswatch/4/solar/bootstrap.min.css"};const themeHelper = new ThemeHelper();让添加 = Object.keys(themes).map(n => themeHelper.add(n, themes[n]));Promise.all(已添加).then(sheets => {console.log(`${sheets.length} 主题加载`);themeHelper.theme = "材料";});
<h3>点击按钮选择主题</h3><按钮class="btn btn-primary"onclick="themeHelper.theme='materia'"> 纸主题按钮><按钮class="btn btn-primary"onclick="themeHelper.theme='flatly'">Flatly 主题按钮><按钮class="btn btn-primary"onclick="themeHelper.theme='太阳能'">太阳能主题</button>
不难看出我完全是关于 ES6 的(也许我过度使用了 const
只是有点 :)
就 Vue 而言,您可以制作一个包装 的组件:
const ThemeHelper = function() {const preloadTheme = (href) =>{let link = document.createElement('link');link.rel = "样式表";链接.href = href;document.head.appendChild(link);返回新的承诺((解决,拒绝)=> {link.onload = e =>{const sheet = e.target.sheet;sheet.disabled = true;解决(工作表);};link.onerror = 拒绝;});};const selectTheme = (themes, name) =>{如果(名称&& !主题[名称]){throw new Error(`"${name}" 尚未定义为主题.`);}Object.keys(themes).forEach(n => themes[n].disabled = (n !== name));}让主题 = {};返回 {add(name, href) { return preloadTheme(href).then(s => themes[name] = s) },设置主题(名称){选择主题(主题,名称)},获取主题() { 返回对象.keys(主题).find(n => !themes[n].disabled) }};};让应用程序 = 新 Vue({el: '#app',数据() {返回 {主题:{断然:https://bootswatch/4/flatly/bootstrap.min.css",材料:https://bootswatch/4/materia/bootstrap.min.css",太阳能:https://bootswatch/4/solar/bootstrap.min.css"},主题助手:新主题助手(),加载:真实,}},创建(){//添加/加载主题让添加 = Object.keys(this.themes).map(name => {返回 this.themeHelper.add(name, this.themes[name]);});Promise.all(已添加).then(sheets => {console.log(`${sheets.length} 主题加载`);this.loading = false;this.themeHelper.theme = "flatly";});}});
<script src="https://unpkg/vue@2.5.2/dist/vue.js"><;/脚本><div id="应用程序"><p v-if="正在加载">正在加载...</p><select v-model="themeHelper.theme"><option v-for="(href, name) of themes" v-bind:value="name">{{ 姓名 }}</选项></选择><span>已选择:{{ themeHelper.theme }}</span><小时><h3>选择上面的主题</h3><button class="btn btn-primary">一个按钮</button>
我希望这对你有用,因为它对我来说很有趣!
I have a VueJS app that will come with many different themes (at least 20 or so). Each theme stylesheet not only changes things like color and font size, but also the position and layout of some elements as well.
I want the user to be able to switch between these themes dynamically. So, at runtime, the user will be able to open an Options menu and select from a dropdown.
What is the cleanest way to have many dynamic user-selectable themes in VueJS?
I've thought of a couple of ways, such as:
Dynamically inserting a<link>
or <style>
tag. While this might work, I don't really see it as particularly "clean", and if I'm loading from AJAX, then oftentimes I'll see a FOUC.
Simply changing the Vue class bindings through a computed property. Something like having a if-else
chain for every supported theme in every component. I don't particularly like this solution, as then every component I make will need to be updated every time I add a new theme later on.
In React, I think there's a plugin or something that has a <ThemeProvider>
component, where adding a theme is as simple as wrapping it, i.e. <ThemeProvider theme={themeProp}><MyComponent></ThemeProvider>
, and all styles in that theme will apply to that component and all child components.
Does VueJS have something similar, or is there a way to implement it?
解决方案I will admit I had some fun with this one. This solution does not depend on Vue, but it can easily by used by Vue. Here we go!
My goal is to create a "particularly clean" dynamic insertion of <link>
stylesheets which should not result in a FOUC.
I created a class (technically, it's a constructor function, but you know what I mean) called ThemeHelper
, which works like this:
myThemeHelper.add(themeName, href)
will preload a stylesheet from href
(a URL) with stylesheet.disabled = true
, and give it a name (just for keeping track of it). This returns a Promise
that resolves to a CSSStyleSheet when the stylesheet's onload
is called.
myThemeHelper.theme = "<theme name>"
(setter) select a theme to apply. The previous theme is disabled, and the given theme is enabled. The switch happens quickly because the stylesheet has already been pre-loaded by .add
.
myThemeHelper.theme
(getter) returns the current theme name.
The class itself is 33 lines. I made a snippet that switches between some Bootswatch themes, since those CSS files are pretty large (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/4/flatly/bootstrap.min.css",
materia: "https://bootswatch/4/materia/bootstrap.min.css",
solar: "https://bootswatch/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>
It is not hard to tell that I'm all about ES6 (and maybe I overused const
just a bit :)
As far as Vue goes, you could make a component that wraps a <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/4/flatly/bootstrap.min.css",
materia: "https://bootswatch/4/materia/bootstrap.min.css",
solar: "https://bootswatch/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/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>
I hope this is as useful to you as it was fun for me!
这篇关于使用 Vue.js 用户可切换的自定义主题的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!
更多推荐
[db:关键词]
发布评论