Front-end page theme switching scheme

Solution 1: Dynamic import of link tags


The method is to prepare several sets of CSS theme style files in advance, and when needed, create a link tag to dynamically load into the head tag, or dynamically change the href attribute of the link tag.

advantage:

  • Implemented on-demand loading and improved the performance of the first screen loading

shortcoming:

  • Dynamically load style files. If the file is too large and the network condition is poor, there may be a delay in loading, resulting in unsmooth style switching

  • If not properly defined in the theme style sheet, there will be priority issues

  • Each theme style is hard-coded, and it is also very troublesome to modify or add a theme to a theme style sheet later

Solution 2: Introduce all theme styles in advance and switch class names


This solution is similar to the first one. In order to solve the problem of repeatedly loading style files, all styles are introduced in advance, and the specified root element class name is replaced when the theme needs to be switched, which is equivalent to directly overriding the style. The following styles are replaced uniformly. The basic method is as follows:

/* day样式主题 */
body.day .box {
color: #f90;
background: #fff;
}
/* dark样式主题 */
body.dark .box {
color: #eee;
background: #333;
}
.box {
width: 100px;
height: 100px;
border: 1px solid #000;
}
<div class="box">
<p>hello</p>
</div>
<p>  
选择样式:
<button onclick="change('day')">day</button>
<button onclick="change('dark')">dark</button>
</p>
functionchange(theme) {
document.body.className = theme;
}

advantage:

  • There is no need to reload the style file, and there will be no lag when switching styles

shortcoming:

  • When the first screen is loaded, some time will be sacrificed to load style resources

  • There can also be priority issues if not properly defined within the theme's stylesheet

  • Each theme style is hard-coded, and it is also very troublesome to modify or add a theme to a theme style sheet later

Solution 3: CSS variable + class name switching


Inspiration reference: Vue3 official website

There is a dark mode switching button on the Vue3 official website, which will transition smoothly after clicking. Although there is also a v-bind feature in Vue3 that can realize dynamic style binding, after observation, the Vue official website does not adopt this solution. For Vue3 v The -bind feature will be elaborated in the next plan.

The general idea is similar to solution 2. It is still to load the style file in advance, and replace the specified root element class name when switching. However, what is relatively flexible here is that CSS variables are defined in the root scope by default, and you only need to change the corresponding values ​​of CSS variables under different themes.

By the way, color-scheme: dark is also used on the Vue3 official website; the system scroll bar is set to black mode to make the style more uniform.

html.dark {

color-scheme: dark;

}

The implementation plan is as follows:

/* 定义根作用域下的变量 */
:root {  --theme-color: #333;  --theme-background: #eee;}
/* 更改dark类名下变量的取值 */
.dark{  --theme-color: #eee;  --theme-background: #333;}
/* 更改pink类名下变量的取值 */
.pink{  --theme-color: #fff;  --theme-background: pink;}
.box {transition: all .2s;width: 100px;height: 100px;border: 1px solid #000;
/* 使用变量 */
color: var(--theme-color);
background: var(--theme-background);
}

The performance is as follows:

advantage:

  • There is no need to reload the style file, and there will be no lag when switching styles

  • Just use var() to bind variables where you need to switch the theme, there is no priority problem

  • Adding or modifying themes is convenient and flexible, only need to add or modify CSS variables, and it will be automatically replaced at the place where var() binds style variables

shortcoming:

  • IE compatibility (negligible)

  • When the first screen is loaded, some time will be sacrificed to load style resources

方案4:Vue3新特性(v-bind)


虽然这种方式存在局限性只能在Vue开发中使用,但是为Vue项目开发者做动态样式更改提供了又一个不错的方案。

简单用法

<script setup>
// 这里可以是原始对象值,也可以是ref()或reactive()包裹的值,根据具体需求而定
const theme = {    color: 'red'  }
</script>
<template>
<p>hello</p>
</template>
<style scoped>
p {color: v-bind('theme.color');
}</style>

Vue3中在style样式通过v-bind()绑定变量的原理其实就是给元素绑定CSS变量,在绑定的数据更新时调用CSSStyleDeclaration.setProperty更新CSS变量值。

前面方案3基于CSS变量绑定样式是在:root上定义变量,然后在各个地方都可以获取到根元素上定义的变量。现在的方案我们需要考虑的问题是,如果是基于JS层面如何在各个组件上优雅地使用统一的样式变量?

我们可以利用Vuex或Pinia对全局样式变量做统一管理,如果不想使用类似的插件也可以自行封装一个hook,大致如下:

// 定义暗黑主题变量
exportdefault { 
 fontSize: '16px', 
 fontColor: '#eee',
 background: '#333',};
// 定义白天主题变量
exportdefault {  
fontSize: '20px',  
fontColor: '#f90',  
background: '#eee',
};
import { shallowRef } from'vue';
// 引入主题
import theme_day from'./theme_day';
import theme_dark from'./theme_dark';
// 定义在全局的样式变量
const theme = shallowRef({});
exportfunctionuseTheme() {
// 尝试从本地读取
const localTheme = localStorage.getItem('theme'); 
theme.value = localTheme ? JSON.parse(localTheme) : theme_day;constsetDayTheme = () => {    
theme.value = theme_day;  
};
constsetDarkTheme = () => {   
 theme.value = theme_dark;
  };
return {    
theme,    
setDayTheme,    
setDarkTheme,
  };
}

使用自己封装的主题hook

<script setuplang="ts">
import { useTheme } from'./useTheme.ts';
importMyButtonfrom'./components/MyButton.vue';
const { theme } = useTheme();
</script>
<template>
<div class="box">
<span>Hello</span>
</div>
<my-button />
</template>
<style lang="scss">
.box {
width: 100px;height: 100px;
background: v-bind('theme.background');
color: v-bind('theme.fontColor');
font-size: v-bind('theme.fontSize');
}
</style>
<script setuplang="ts">
import { useTheme } from'../useTheme.ts';
const { theme, setDarkTheme, setDayTheme } = useTheme();constchange1 = () => {setDarkTheme();
};
constchange2 = () => {setDayTheme();};
</script>
<template>
<button class="my-btn" @click="change1">dark</button>
<button class="my-btn" @click="change2">day</button>
</template>
<style scopedlang="scss">
.my-btn {
color: v-bind('theme.fontColor');
background: v-bind('theme.background');
}
</style>

其实从这里可以看到,跟Vue的响应式原理一样,只要数据发生改变,Vue就会把绑定了变量的地方通通更新。

优点:

  • 不用重新加载样式文件,在样式切换时不会有卡顿

  • 在需要切换主题的地方利用v-bind绑定变量即可,不存在优先级问题

  • 新增或修改主题方便灵活,仅需新增或修改JS变量即可,在v-bind()绑定样式变量的地方就会自动更换

缺点:

  • IE兼容性(忽略不计)

  • 首屏加载时会牺牲一些时间加载样式资源

  • 这种方式只要是在组件上绑定了动态样式的地方都会有对应的编译成哈希化的CSS变量,而不像方案3统一地就在:root上设置(不确定在达到一定量级以后的性能),也可能正是如此,Vue官方也并未采用此方式做全站的主题切换

方案5:SCSS + mixin + 类名切换


主要是运用SCSS的混合+CSS类名切换,其原理主要是将使用到mixin混合的地方编译为固定的CSS以后,再通过类名切换去做样式的覆盖,实现方案如下:

定义SCSS变量

/* 字体定义规范 */
$font_samll:12Px;$font_medium_s:14Px;$font_medium:16Px;$font_large:18Px;
/* 背景颜色规范(主要) */
$background-color-theme: #d43c33;
//背景主题颜色默认(网易红)
$background-color-theme1: #42b983;
//背景主题颜色1(QQ绿)
$background-color-theme2: #333;
//背景主题颜色2(夜间模式)
/* 背景颜色规范(次要) */
$background-color-sub-theme: #f5f5f5;
//背景主题颜色默认(网易红)
$background-color-sub-theme1: #f5f5f5;
//背景主题颜色1(QQ绿)
$background-color-sub-theme2: #444;
//背景主题颜色2(夜间模式)
/* 字体颜色规范(默认) */
$font-color-theme : #666;
//字体主题颜色默认(网易)
$font-color-theme1 : #666;
//字体主题颜色1(QQ)
$font-color-theme2 : #ddd;
//字体主题颜色2(夜间模式)
/* 字体颜色规范(激活) */
$font-active-color-theme : #d43c33;
//字体主题颜色默认(网易红)
$font-active-color-theme1 : #42b983;
//字体主题颜色1(QQ绿)
$font-active-color-theme2 : #ffcc33;
//字体主题颜色2(夜间模式)
/* 边框颜色 */
$border-color-theme : #d43c33;
//边框主题颜色默认(网易)
$border-color-theme1 : #42b983;
//边框主题颜色1(QQ)
$border-color-theme2 : #ffcc33;
//边框主题颜色2(夜间模式)
/* 字体图标颜色 */
$icon-color-theme : #ffffff;
//边框主题颜色默认(网易)
$icon-color-theme1 : #ffffff;
//边框主题颜色1(QQ)
$icon-color-theme2 : #ffcc2f;
//边框主题颜色2(夜间模式)
$icon-theme : #d43c33;
//边框主题颜色默认(网易)
$icon-theme1 : #42b983;
//边框主题颜色1(QQ)
$icon-theme2 : #ffcc2f;
//边框主题颜色2(夜间模式)

定义混合mixin

@import"./variable.scss";
@mixin bg_color(){
background: $background-color-theme;  
[data-theme=theme1] & {
background: $background-color-theme1;  
}  
[data-theme=theme2] & {
background: $background-color-theme2;
  }}
@mixin bg_sub_color(){
background: $background-color-sub-theme;  
[data-theme=theme1] & {background: $background-color-sub-theme1;  
}  
[data-theme=theme2] & {
background: $background-color-sub-theme2;  
}}
@mixin font_color(){color: $font-color-theme;  
[data-theme=theme1] & {color: $font-color-theme1;  
}  
[data-theme=theme2] & {
color: $font-color-theme2;  
}}
@mixin font_active_color(){
color: $font-active-color-theme;  
[data-theme=theme1] & {
color: $font-active-color-theme1;  
}  
[data-theme=theme2] & {
color: $font-active-color-theme2;  
}}
@mixin icon_color(){
color: $icon-color-theme;    
[data-theme=theme1] & {
color: $icon-color-theme1;    
}    
[data-theme=theme2] & {
color: $icon-color-theme2;    
}}
@mixin border_color(){
border-color: $border-color-theme;  
[data-theme=theme1] & {
border-color: $border-color-theme1;  
}  
[data-theme=theme2] & {border-color: $border-color-theme2;  
}}
<template>
<div class="header" @click="changeTheme">
<div class="header-left">
<slot name="left">左边</slot>
</div>
<slot name="center"class="">中间</slot>
<div class="header-right">
<slot name="right">右边</slot>
</div>
</div>
</template>
<script>
exportdefault {    
name: 'Header',    
methods: {      
changeTheme () {
document.documentElement.setAttribute('data-theme', 'theme1')      
}    
}  
}</script>
<style scopedlang="scss">
@import"../assets/css/variable";
@import"../assets/css/mixin";
.header{width: 100%;height: 100px;
font-size: $font_medium;
@include bg_color();
}
</style>

这种方案最后得到的结果与方案2类似,只是在定义主题时由于是直接操作的SCSS变量,会更加灵活。

优点:

  • 不用重新加载样式文件,在样式切换时不会有卡顿

  • 在需要切换主题的地方利用mixin混合绑定变量即可,不存在优先级问题

  • 新增或修改主题方便灵活,仅需新增或修改SCSS变量即可,经过编译后会将所有主题全部编译出来

缺点:

  • 首屏加载时会牺牲一些时间加载样式资源

方案6:CSS变量+动态setProperty


此方案较于前几种会更加灵活,不过视情况而定,这个方案适用于由用户根据颜色面板自行设定各种颜色主题,这种是主题颜色不确定的情况,而前几种方案更适用于定义预设的几种主题。

方案参考:vue-element-plus-admin

主要实现思路如下:

只需在全局中设置好预设的全局CSS变量样式,无需单独为每一个主题类名下重新设定CSS变量值,因为主题是由用户动态决定。

:root {  --theme-color: #333;  --theme-background: #eee;}

定义一个工具类方法,用于修改指定的CSS变量值,调用的是CSSStyleDeclaration.setProperty

exportconstsetCssVar = (prop: string, val: any, dom = document.documentElement) => {  dom.style.setProperty(prop, val)}

在样式发生改变时调用此方法即可

setCssVar('--theme-color', color)

优点:

  • 不用重新加载样式文件,在样式切换时不会有卡顿

  • 仔细琢磨可以发现其原理跟方案4利用Vue3的新特性v-bind是一致的,只不过此方案只在:root上动态更改CSS变量而Vue3中会将CSS变量绑定到任何依赖该变量的节点上。

  • 需要切换主题的地方只用在:root上动态更改CSS变量值即可,不存在优先级问题

  • 新增或修改主题方便灵活

缺点:

  • IE兼容性(忽略不计)

  • 首屏加载时会牺牲一些时间加载样式资源(相对于前几种预设好的主题,这种方式的样式定义在首屏加载基本可以忽略不计)

方案总结


说明:两种主题方案都支持并不代表一定是最佳方案,视具体情况而定。

方案/主题样式

固定预设主题样式

主题样式不固定

方案1:link标签动态引入

√(文件过大,切换延时,不推荐)

×

方案2:提前引入所有主题样式,做类名切换

×

方案3:CSS变量+类名切换

√(推荐)

×

方案4:Vue3新特性(v-bind)

√(性能不确定)

√(性能不确定)

方案5:SCSS + mixin + 类名切换

√(推荐,最终呈现效果与方案2类似,但定义和使用更加灵活)

×

方案6:CSS变量+动态setProperty

√(更推荐方案3)

√(推荐)

本文转载,仅供自己学习参考使用!!!

Guess you like

Origin blog.csdn.net/qq_45144083/article/details/129281248