1、link标签动态引入
其做法就是提前准备好几套CSS主题样式文件,在需要的时候,创建link标签动态加载到head标签中,或者是动态改变link标签的href属性
优点:实现了按需加载,提高了首屏加载时的性能
缺点:
- 动态加载样式文件,如果文件过大网络情况不佳的情况下可能会有加载延迟,导致样式切换不流畅
- 如果主题样式表内定义不当,也会有优先级问题
- 各个主题样式是写死的,后续针对某一主题样式表修改或者新增主题也很麻烦
2、提前引入所有主题样式,做类名切换
不同的样式定义不同的类名,切换主题,修改对应的类名即可
javascript
/* day样式主题 */
body.day .box {
color: #f90;
background: #fff;
}
/* dark样式主题 */
body.dark .box {
color: #eee;
background: #333;
}
优点:不用重新加载样式文件,在样式切换时不会有卡顿
缺点:
- 首屏加载时会牺牲一些时间加载样式资源
- 如果主题样式表内定义不当,也会有优先级问题
- 各个主题样式是写死的,后续针对某一主题样式表修改或者新增主题也很麻烦
3、 CSS变量+类名切换
首先把所有的样式文件在初始化的时候就加载进来,切换时候将指定的根元素类名进行切换,默认在根做作用域下定义好CSS变量,只需在不同的主题下更改CSS变量对应的取值即可
实现方案如下:
javascript
:root {
--theme-color: red;
--theme-background: #eee;
}
.pink{
--theme-color: yellow;
--theme-background: pink;
}
.box{
color: var(--theme-color);
}
优点:
- 不用重新加载样式文件,在样式切换时不会有卡顿
- 在需要切换主题的地方利用var()绑定变量即可,不存在优先级问题
- 新增或修改主题方便灵活,仅需新增或修改CSS变量即可,在var()绑定样式变量的地方就会自动更换
缺点:首屏加载时会牺牲一些时间加载样式资源
4、Vue3新特性(v-bind)
简单用法:
javascript
<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,大致如下:
javascript
// 定义暗黑主题变量
export default {
fontSize: '16px',
fontColor: '#eee',
background: '#333',
};
// 定义白天主题变量
export default {
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({});
export function useTheme() {
// 尝试从本地读取
const localTheme = localStorage.getItem('theme');
theme.value = localTheme ? JSON.parse(localTheme) : theme_day;
const setDayTheme = () => {
theme.value = theme_day;
};
const setDarkTheme = () => {
theme.value = theme_dark;
};
return {
theme,
setDayTheme,
setDarkTheme,
};
}
使用自己封装的主题hook
javascript
<script setup lang="ts">
import { useTheme } from './useTheme.ts';
import MyButton from './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 setup lang="ts">
import { useTheme } from '../useTheme.ts';
const { theme, setDarkTheme, setDayTheme } = useTheme();
const change1 = () => {
setDarkTheme();
};
const change2 = () => {
setDayTheme();
};
</script>
<template>
<button class="my-btn" @click="change1">dark</button>
<button class="my-btn" @click="change2">day</button>
</template>
<style scoped lang="scss">
.my-btn {
color: v-bind('theme.fontColor');
background: v-bind('theme.background');
}
</style>
优点:
- 不用重新加载样式文件,在样式切换时不会有卡顿
- 在需要切换主题的地方利用v-bind绑定变量即可,不存在优先级问题
- 新增或修改主题方便灵活,仅需新增或修改JS变量即可,在v-bind()绑定样式变量的地方就会自动更换
缺点:
- 首屏加载时会牺牲一些时间加载样式资源
- 这种方式只要是在组件上绑定了动态样式的地方都会有对应的编译成哈希化的CSS变量,而不像方案3统一地就在:root上设置(不确定在达到一定量级以后的性能),也可能正是如此,Vue官方也并未采用此方式做全站的主题切换
5、SCSS+mixin+类名切换
SCSS的混合+CSS类名切换,主要是将使用到mixin混合的地方编译为固定的CSS以后,再通过类名切换去做样式的覆盖
javascript
// 定义scss变量
/* 背景颜色规范(主要) */
$background-color-theme: #d43c33;
$background-color-theme1: #42b983;
$background-color-theme2: #333;
// 定义混合mixin
@mixin bg_color(){
background: $background-color-theme;
[data-theme=theme1] & {
background: $background-color-theme1;
}
[data-theme=theme2] & {
background: $background-color-theme2;
}
}
// 设置对应的主题
test2(){
document.documentElement.setAttribute('data-theme', 'theme1')
}
// 需要展示样式的地方
.test{
@include bg_color()
}
优点:
- 不用重新加载样式文件,在样式切换时不会有卡顿
- 在需要切换主题的地方利用mixin混合绑定变量即可,不存在优先级问题
- 新增或修改主题方便灵活,仅需新增或修改SCSS变量即可,经过编译后会将所有主题全部编译出来
缺点:首屏加载时会牺牲一些时间加载样式资源
6、CSS变量+动态setProperty
这种方案多用于颜色不确定时用,前面几种适用于颜色确定的时的方案
javascript
// 首先定义默认样式
:root {
--theme-color: pink;
--theme-background: #eee;
}
// 封装改变样式的方法
const setCssVar = (prop, val, dom = document.documentElement) => {
dom.style.setProperty(prop, val)
}
export default setCssVar
//调用方法
import setCssVar from '@/utils/test'
test2(){
console.log(setCssVar)
setCssVar('--theme-color', 'red')
}
// 应用主题色的样式
.test{
color: var(--theme-color);
}
优点:
- 不用重新加载样式文件,在样式切换时不会有卡顿
- 仔细琢磨可以发现其原理跟方案4利用Vue3的新特性v-bind是一致的,只不过此方案只在:root上动态更改CSS变量而Vue3中会将CSS变量绑定到任何依赖该变量的节点上。
- 需要切换主题的地方只用在:root上动态更改CSS变量值即可,不存在优先级问题
- 新增或修改主题方便灵活
缺点:首屏加载时会牺牲一些时间加载样式资源(相对于前几种预设好的主题,这种方式的样式定义在首屏加载基本可以忽略不计)