方案1:link标签动态引入
提前准备好几套 CSS 主题样式文件,在需要的时候,创建 link 标签动态加载到 head 标签中,或者是动态改变 link 标签的 href 属性;
- 优点:
- 实现了按需加载,提高了首屏加载时的性能;
- 缺点:
- 动态加载样式文件,如果文件过大网络情况不佳的情况下可能会有加载延迟,导致样式切换不流畅;
- 如果主题样式表内定义不当,会有优先级问题;
- 各个主题样式是写死的,后续针对某一主题样式表修改或者新增主题也很麻烦;
方案2:提前引入所有主题样式,做类名切换
与第一种比较类似,为了解决反复加载样式文件问题提前将样式全部引入,在需要切换主题的时候将指定的根元素类名更换,相当于直接做了样式覆盖,在该类名下的各个样式就统一地更换了;
-
示例代码
<style> /* 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; } </style> <div class="box"> <p>hello</p> </div> <p> 选择样式: <button onclick="change('day')">day</button> <button onclick="change('dark')">dark</button> </p> <script> function change(theme) { document.body.className = theme; } </script>
-
优点:
- 不用重新加载样式文件,在样式切换时不会有卡顿;
-
缺点:
- 首屏加载时会牺牲一些时间加载样式资源;
- 如果主题样式表内定义不当,也会有优先级问题;
- 各个主题样式是写死的,后续针对某一主题样式表修改或者新增主题也很麻烦;
方案3:CSS变量+类名切换
大体思路跟方案2相似,依然是提前将样式文件载入,切换时将指定的根元素类名更换;
不过这里相对灵活的是,默认在根作用域下定义好 CSS 变量,只需要在不同的主题下更改 CSS 变量对应的取值即可;
-
示例代码
/* 定义根作用域下的变量 */ :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); }
-
优点:
- 不用重新加载样式文件,在样式切换时不会有卡顿;
- 在需要切换主题的地方利用 var() 绑定变量即可,不存在优先级问题;
- 新增或修改主题方便灵活,仅需新增或修改 CSS 变量即可,在 var() 绑定样式变量的地方就会自动更换;
-
缺点:
- 首屏加载时会牺牲一些时间加载样式资源;
方案4:Vue3 新特性(v-bind)
-
简单用法
<template> <p>hello</p> </template> <script setup> // 这里可以是原始对象值,也可以是ref()或reactive()包裹的值,根据具体需求而定 const theme = { color: 'red' } </script> <style scoped> p { // Vue3 中在 style 样式通过 v-bind() 绑定变量的原理其实就是给元素绑定CSS变量,在绑定的数据更新时调用 CSSStyleDeclaration.setProperty 更新 CSS 变量值 color: v-bind('theme.color'); } </style>
-
示例代码
- useTheme.ts
// 定义暗黑主题变量 export theme_dark { fontSize: '16px', fontColor: '#eee', background: '#333', }; // 定义白天主题变量 export theme_day { fontSize: '20px', fontColor: '#f90', background: '#eee', }; import { shallowRef } from 'vue'; // 定义在全局的样式变量 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, }; }
- test.vue
<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>
- useTheme.ts
-
优点:
- 不用重新加载样式文件,在样式切换时不会有卡顿;
- 在需要切换主题的地方利用v-bind绑定变量即可,不存在优先级问题;
- 新增或修改主题方便灵活,仅需新增或修改JS变量即可,在v-bind()绑定样式变量的地方就会自动更换;
-
缺点:
- 首屏加载时会牺牲一些时间加载样式资源;
- 这种方式只要是在组件上绑定了动态样式的地方都会有对应的编译成哈希化的CSS变量,而不像方案3统一地就在:root上设置(不确定在达到一定量级以后的性能),也可能正是如此,Vue官方也并未采用此方式做全站的主题切换;
方案5:SCSS + mixin + 类名切换
主要是运用 SCSS 的混合 + CSS 类名切换,其原理主要是将使用到 mixin 混合的地方编译为固定的 CSS 以后,再通过类名切换去做样式的覆盖;
这种方案最后得到的结果与方案 2 类似,只是在定义主题时由于是直接操作的 SCSS 变量,会更加灵活;
-
示例代码:
- 定义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; } }
- test.vue
<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> export default { name: 'Header', methods: { changeTheme () { document.documentElement.setAttribute('data-theme', 'theme1') } } } </script> <style scoped lang="scss"> @import "../assets/css/variable"; @import "../assets/css/mixin"; .header{ width: 100%; height: 100px; font-size: $font_medium; @include bg_color(); } </style>
- 定义SCSS变量:
-
优点:
- 不用重新加载样式文件,在样式切换时不会有卡顿;
- 在需要切换主题的地方利用 mixin 混合绑定变量即可,不存在优先级问题;
- 新增或修改主题方便灵活,仅需新增或修改 SCSS 变量即可,经过编译后会将所有主题全部编译出来;
-
缺点:
- 首屏加载时会牺牲一些时间加载样式资源;
方案6:CSS 变量 + 动态 setProperty
只需在全局中设置好预设的全局 CSS 变量样式,无需单独为每一个主题类名下重新设定 CSS 变量值,因为主题是由用户动态决定;
修改指定 dom 的 CSS 变量值(dom.style.setProperty(‘–theme-color’, red)),使用 css 变量 color:var(–theme-color);
该博客中的一级导航「分类」就是使用了该方案;
-
优点:
- 不用重新加载样式文件,在样式切换时不会有卡顿;
- 仔细琢磨可以发现其原理跟方案4利用Vue3的新特性v-bind是一致的,只不过此方案只在:root上动态更改CSS变量而Vue3中会将CSS变量绑定到任何依赖该变量的节点上;
- 需要切换主题的地方只用在:root上动态更改CSS变量值即可,不存在优先级问题;
- 新增或修改主题方便灵活;
-
缺点:
- 首屏加载时会牺牲一些时间加载样式资源(相对于前几种预设好的主题,这种方式的样式定义在首屏加载基本可以忽略不计);
方案总结
方案 | 固定预设主题样式 | 主题样式不固定 |
---|---|---|
方案1:link标签动态引入 | ✅(文件过大,切换延时,不推荐) | ❌ |
方案2:提前引入所有主题样式,做类名切换 | ✅ | ❌ |
方案3:CSS变量+类名切换 | ✅(推荐) | ❌ |
方案4:Vue3新特性(v-bind) | ✅ | ✅ |
方案5:SCSS + mixin + 类名切换 | ✅(推荐,最终呈现效果与方案2类似,但定义和使用更加灵活) | ❌ |
方案6:CSS变量+动态setProperty | ✅(更推荐方案3) | ✅(推荐) |
动画✍️ 日历卡片翻动效果
上一篇